odfdo

odfdo

Python library for OpenDocument format (ODF)

logo

odfdo is a Python3 library implementing the ISO/IEC 26300 OpenDocument Format standard.

Project: https://github.com/jdum/odfdo

Author: jerome.dumonteil@gmail.com

License: Apache License, Version 2.0

odfdo is a derivative work of the former lpod-python project.

Installation

Installation from Pypi (recommended):

pip install odfdo

Installation from sources (requiring setuptools):

pip install .

After installation from sources, you can check everything is working (some requirements: pytest, Pillow, ...):

pytest

The tests should run for a few seconds or minutes and issue no error.

Usage

from odfdo import Document, Paragraph

doc = Document('text')
doc.body.append(Paragraph("Hello world!"))
doc.save("hello.odt")

tl;dr

'Intended Audience :: Developers'

Documentation

There is no detailed documentation or tutorial, but:

  • the recipes folder contains more than 50 working sample scripts,
  • the doc folder contains an auto generated documentation.

When installing odfdo, a few scripts are installed:

  • odfdo-diff: show a diff between two .odt document.
  • odfdo-folder: convert standard ODF file to folder and files, and reverse.
  • odfdo-show: dump text from an ODF file to the standard output, and optionally styles and meta informations.
  • odfdo-styles: command line interface tool to manipulate styles of ODF files.
  • odfdo-replace: find a pattern (regex) in an ODF file and replace by some string.
  • odfdo-highlight: highlight the text matching a pattern (regex) in an ODF file.
  • odfdo-headers: print the headers of an ODF file.
  • odfdo-table-shrink: Shrink tables to optimize width and height (experimental).

About styles: the best way to apply style is by merging styles from a template document into your generated document (See odfdo-styles script). Styles are a complex matter in ODF, so trying to generate styles programmatically is not recommended.

Limitations

odfdo is intended to facilitate the generation of ODF documents, nevertheless a basic knowledge of the ODF format is necessary.

ODF document rendering can vary greatly from software to software. Especially the "styles" of the document allow an adaptation of the rendering for a particular software.

The best (only ?) way to apply style is by merging styles from a template document into your generated document.

Related project

I you work on .ods files (spreadsheet), you may be interested by these scripts that use this library to parse/generate .ods files: https://github.com/jdum/odsgenerator and https://github.com/jdum/odsparsator

Changes from former lpod library

lpod-python was written in 2009-2010 as a Python 2.x library, see: https://github.com/lpod/lpod-python

odfdo is an adaptation of this former project. odfdo main changes from lpod:

  • odfdo requires Python version 3.9 to 3.12. For Python 3.6 to 3.8 see previous releases.
  • API change: more pythonic.
  • include recipes.
  • use Apache 2.0 license.
  1# Copyright 2018-2024 Jérôme Dumonteil
  2# Copyright (c) 2009-2010 Ars Aperta, Itaapy, Pierlis, Talend.
  3#
  4# Licensed under the Apache License, Version 2.0 (the "License");
  5# you may not use this file except in compliance with the License.
  6# You may obtain a copy of the License at
  7#
  8#     http://www.apache.org/licenses/LICENSE-2.0
  9#
 10# Unless required by applicable law or agreed to in writing, software
 11# distributed under the License is distributed on an "AS IS" BASIS,
 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13# See the License for the specific language governing permissions and
 14# limitations under the License.
 15#
 16#
 17# Authors (odfdo project): jerome.dumonteil@gmail.com
 18# The odfdo project is a derivative work of the lpod-python project:
 19# https://github.com/lpod/lpod-python
 20# Authors: David Versmisse <david.versmisse@itaapy.com>
 21#          Hervé Cauwelier <herve@itaapy.com>
 22#          Romain Gauthier <romain@itaapy.com>
 23"""
 24.. include:: ../README.md
 25"""
 26
 27__all__ = [
 28    "AnimPar",
 29    "AnimSeq",
 30    "AnimTransFilter",
 31    "Annotation",
 32    "AnnotationEnd",
 33    "BackgroundImage",
 34    "Bookmark",
 35    "BookmarkEnd",
 36    "BookmarkStart",
 37    "Cell",
 38    "ChangeInfo",
 39    "Column",
 40    "ConnectorShape",
 41    "Container",
 42    "Content",
 43    "Content",
 44    "Document",
 45    "DrawFillImage",
 46    "DrawGroup",
 47    "DrawImage",
 48    "DrawPage",
 49    "Element",
 50    "ElementTyped",
 51    "EllipseShape",
 52    "FIRST_CHILD",
 53    "Frame",
 54    "Header",
 55    "HeaderRows",
 56    "IndexTitle",
 57    "IndexTitleTemplate",
 58    "LAST_CHILD",
 59    "LineBreak",
 60    "LineShape",
 61    "Link",
 62    "List",
 63    "ListItem",
 64    "Manifest",
 65    "Meta",
 66    "NEXT_SIBLING",
 67    "NamedRange",
 68    "Note",
 69    "PREV_SIBLING",
 70    "PageBreak",
 71    "Paragraph",
 72    "RectangleShape",
 73    "Reference",
 74    "ReferenceMark",
 75    "ReferenceMarkEnd",
 76    "ReferenceMarkStart",
 77    "Row",
 78    "RowGroup",
 79    "Section",
 80    "Spacer",
 81    "Span",
 82    "Style",
 83    "Styles",
 84    "TOC",
 85    "Tab",
 86    "TabStopStyle",
 87    "Table",
 88    "Text",
 89    "TextChange",
 90    "TextChangeEnd",
 91    "TextChangeStart",
 92    "TextChangedRegion",
 93    "TextDeletion",
 94    "TextFormatChange",
 95    "TextInsertion",
 96    "TocEntryTemplate",
 97    "TrackedChanges",
 98    "UserDefined",
 99    "UserFieldDecl",
100    "UserFieldDecls",
101    "UserFieldGet",
102    "UserFieldInput",
103    "VarChapter",
104    "VarCreationDate",
105    "VarCreationTime",
106    "VarDate",
107    "VarDecl",
108    "VarDecls",
109    "VarDescription",
110    "VarFileName",
111    "VarGet",
112    "VarInitialCreator",
113    "VarKeywords",
114    "VarPageCount",
115    "VarPageNumber",
116    "VarSet",
117    "VarSubject",
118    "VarTime",
119    "VarTitle",
120    "XmlPart",
121    "__version__",
122    "create_table_cell_style",
123    "default_boolean_style",
124    "default_currency_style",
125    "default_date_style",
126    "default_frame_position_style",
127    "default_number_style",
128    "default_percentage_style",
129    "default_time_style",
130    "default_toc_level_style",
131    "hex2rgb",
132    "hexa_color",
133    "make_table_cell_border_string",
134    "rgb2hex",
135]
136
137
138from .bookmark import Bookmark, BookmarkEnd, BookmarkStart
139from .cell import Cell
140from .container import Container
141from .content import Content
142from .document import Document
143from .draw_page import DrawPage
144from .element import FIRST_CHILD, LAST_CHILD, NEXT_SIBLING, PREV_SIBLING, Element, Text
145from .element_typed import ElementTyped
146from .frame import Frame, default_frame_position_style
147from .header import Header
148from .header_rows import HeaderRows
149from .image import DrawFillImage, DrawImage
150from .link import Link
151from .list import List, ListItem
152from .manifest import Manifest
153from .meta import Meta
154from .note import Annotation, AnnotationEnd, Note
155from .paragraph import LineBreak, PageBreak, Paragraph, Spacer, Span, Tab
156from .reference import Reference, ReferenceMark, ReferenceMarkEnd, ReferenceMarkStart
157from .section import Section
158from .shapes import ConnectorShape, DrawGroup, EllipseShape, LineShape, RectangleShape
159from .smil import AnimPar, AnimSeq, AnimTransFilter
160from .style import (
161    BackgroundImage,
162    Style,
163    create_table_cell_style,
164    default_boolean_style,
165    default_currency_style,
166    default_date_style,
167    default_number_style,
168    default_percentage_style,
169    default_time_style,
170    make_table_cell_border_string,
171)
172from .styles import Styles
173from .table import Column, NamedRange, Row, RowGroup, Table
174from .toc import (
175    TOC,
176    IndexTitle,
177    IndexTitleTemplate,
178    TabStopStyle,
179    TocEntryTemplate,
180    default_toc_level_style,
181)
182from .tracked_changes import (
183    ChangeInfo,
184    TextChange,
185    TextChangedRegion,
186    TextChangeEnd,
187    TextChangeStart,
188    TextDeletion,
189    TextFormatChange,
190    TextInsertion,
191    TrackedChanges,
192)
193from .utils import hex2rgb, hexa_color, rgb2hex
194from .variable import (
195    UserDefined,
196    UserFieldDecl,
197    UserFieldDecls,
198    UserFieldGet,
199    UserFieldInput,
200    VarChapter,
201    VarCreationDate,
202    VarCreationTime,
203    VarDate,
204    VarDecl,
205    VarDecls,
206    VarDescription,
207    VarFileName,
208    VarGet,
209    VarInitialCreator,
210    VarKeywords,
211    VarPageCount,
212    VarPageNumber,
213    VarSet,
214    VarSubject,
215    VarTime,
216    VarTitle,
217)
218from .version import __version__
219from .xmlpart import XmlPart
class AnimPar(odfdo.Element):
32class AnimPar(Element):
33    """A container for SMIL Presentation Animations.
34
35    Arguments:
36
37        presentation_node_type -- default, on-click, with-previous,
38                                  after-previous, timing-root, main-sequence
39                                  and interactive-sequence
40
41        smil_begin -- indefinite, 10s, [id].click, [id].begin
42    """
43
44    _tag = "anim:par"
45    _properties = (
46        PropDef("presentation_node_type", "presentation:node-type"),
47        PropDef("smil_begin", "smil:begin"),
48    )
49
50    def __init__(
51        self,
52        presentation_node_type: str | None = None,
53        smil_begin: str | None = None,
54        **kwargs: Any,
55    ) -> None:
56        super().__init__(**kwargs)
57        if self._do_init:
58            if presentation_node_type:
59                self.presentation_node_type = presentation_node_type
60            if smil_begin:
61                self.smil_begin = smil_begin

A container for SMIL Presentation Animations.

Arguments:

presentation_node_type -- default, on-click, with-previous,
                          after-previous, timing-root, main-sequence
                          and interactive-sequence

smil_begin -- indefinite, 10s, [id].click, [id].begin
AnimPar( presentation_node_type: str | None = None, smil_begin: str | None = None, **kwargs: Any)
50    def __init__(
51        self,
52        presentation_node_type: str | None = None,
53        smil_begin: str | None = None,
54        **kwargs: Any,
55    ) -> None:
56        super().__init__(**kwargs)
57        if self._do_init:
58            if presentation_node_type:
59                self.presentation_node_type = presentation_node_type
60            if smil_begin:
61                self.smil_begin = smil_begin
presentation_node_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
smil_begin: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class AnimSeq(odfdo.Element):
67class AnimSeq(Element):
68    """TA container for SMIL Presentation Animations. Animations
69    inside this block are executed after the slide has executed its initial
70    transition.
71
72    Arguments:
73
74        presentation_node_type -- default, on-click, with-previous,
75                                  after-previous, timing-root, main-sequence
76                                  and interactive-sequence
77    """
78
79    _tag = "anim:seq"
80    _properties = (PropDef("presentation_node_type", "presentation:node-type"),)
81
82    def __init__(
83        self,
84        presentation_node_type: str | None = None,
85        **kwargs: Any,
86    ) -> None:
87        super().__init__(**kwargs)
88        if self._do_init and presentation_node_type:
89            self.presentation_node_type = presentation_node_type

TA container for SMIL Presentation Animations. Animations inside this block are executed after the slide has executed its initial transition.

Arguments:

presentation_node_type -- default, on-click, with-previous,
                          after-previous, timing-root, main-sequence
                          and interactive-sequence
AnimSeq(presentation_node_type: str | None = None, **kwargs: Any)
82    def __init__(
83        self,
84        presentation_node_type: str | None = None,
85        **kwargs: Any,
86    ) -> None:
87        super().__init__(**kwargs)
88        if self._do_init and presentation_node_type:
89            self.presentation_node_type = presentation_node_type
presentation_node_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class AnimTransFilter(odfdo.Element):
 95class AnimTransFilter(Element):
 96    """
 97    Class to make a beautiful transition between two frames.
 98
 99    Arguments:
100      smil_dur -- XXX complete me
101
102      smil_type and smil_subtype -- see http://www.w3.org/TR/SMIL20/
103                    smil-transitions.html#TransitionEffects-Appendix
104                                    to get a list of all types/subtypes
105
106      smil_direction -- forward, reverse
107
108      smil_fadeColor -- forward, reverse
109
110      smil_mode -- in, out
111    """
112
113    _tag = "anim:transitionFilter"
114    _properties = (
115        PropDef("smil_dur", "smil:dur"),
116        PropDef("smil_type", "smil:type"),
117        PropDef("smil_subtype", "smil:subtype"),
118        PropDef("smil_direction", "smil:direction"),
119        PropDef("smil_fadeColor", "smil:fadeColor"),
120        PropDef("smil_mode", "smil:mode"),
121    )
122
123    def __init__(
124        self,
125        smil_dur: str | None = None,
126        smil_type: str | None = None,
127        smil_subtype: str | None = None,
128        smil_direction: str | None = None,
129        smil_fadeColor: str | None = None,
130        smil_mode: str | None = None,
131        **kwargs: Any,
132    ) -> None:
133        super().__init__(**kwargs)
134        if self._do_init:
135            if smil_dur:
136                self.smil_dur = smil_dur
137            if smil_type:
138                self.smil_type = smil_type
139            if smil_subtype:
140                self.smil_subtype = smil_subtype
141            if smil_direction:
142                self.smil_direction = smil_direction
143            if smil_fadeColor:
144                self.smil_fadeColor = smil_fadeColor
145            if smil_mode:
146                self.smil_mode = smil_mode

Class to make a beautiful transition between two frames.

Arguments: smil_dur -- XXX complete me

smil_type and smil_subtype -- see http://www.w3.org/TR/SMIL20/ smil-transitions.html#TransitionEffects-Appendix to get a list of all types/subtypes

smil_direction -- forward, reverse

smil_fadeColor -- forward, reverse

smil_mode -- in, out

AnimTransFilter( smil_dur: str | None = None, smil_type: str | None = None, smil_subtype: str | None = None, smil_direction: str | None = None, smil_fadeColor: str | None = None, smil_mode: str | None = None, **kwargs: Any)
123    def __init__(
124        self,
125        smil_dur: str | None = None,
126        smil_type: str | None = None,
127        smil_subtype: str | None = None,
128        smil_direction: str | None = None,
129        smil_fadeColor: str | None = None,
130        smil_mode: str | None = None,
131        **kwargs: Any,
132    ) -> None:
133        super().__init__(**kwargs)
134        if self._do_init:
135            if smil_dur:
136                self.smil_dur = smil_dur
137            if smil_type:
138                self.smil_type = smil_type
139            if smil_subtype:
140                self.smil_subtype = smil_subtype
141            if smil_direction:
142                self.smil_direction = smil_direction
143            if smil_fadeColor:
144                self.smil_fadeColor = smil_fadeColor
145            if smil_mode:
146                self.smil_mode = smil_mode
smil_dur: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
smil_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
smil_subtype: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
smil_direction: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
smil_fadeColor: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
smil_mode: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Annotation(odfdo.Element):
147class Annotation(Element):
148    """Annotation element credited to the given creator with the
149    given text, optionally dated (current date by default).
150    If name not provided and some parent is provided, the name is
151    autogenerated.
152
153    Arguments:
154
155        text -- str or odf_element
156
157        creator -- str
158
159        date -- datetime
160
161        name -- str
162
163        parent -- Element
164    """
165
166    _tag = "office:annotation"
167    _properties = (
168        PropDef("name", "office:name"),
169        PropDef("note_id", "text:id"),
170    )
171
172    def __init__(
173        self,
174        text_or_element: Element | str | None = None,
175        creator: str | None = None,
176        date: datetime | None = None,
177        name: str | None = None,
178        parent: Element | None = None,
179        **kwargs: Any,
180    ) -> None:
181        # fixme : use offset
182        # TODO allow paragraph and text styles
183        super().__init__(**kwargs)
184
185        if self._do_init:
186            self.note_body = text_or_element  # type:ignore
187            if creator:
188                self.dc_creator = creator
189            if date is None:
190                date = datetime.now()
191            self.dc_date = date
192            if not name:
193                name = get_unique_office_name(parent)
194                self.name = name
195
196    @property
197    def note_body(self) -> str:
198        return self.text_content
199
200    @note_body.setter
201    def note_body(self, text_or_element: Element | str | None) -> None:
202        if text_or_element is None:
203            self.text_content = ""
204        elif isinstance(text_or_element, str):
205            self.text_content = text_or_element
206        elif isinstance(text_or_element, Element):
207            self.clear()
208            self.append(text_or_element)
209        else:
210            raise TypeError(f'Unexpected type for body: "{type(text_or_element)}"')
211
212    @property
213    def start(self) -> Element:
214        """Return self."""
215        return self
216
217    @property
218    def end(self) -> Element | None:
219        """Return the corresponding annotation-end tag or None."""
220        name = self.name
221        parent = self.parent
222        if parent is None:
223            raise ValueError("Can't find end tag: no parent available")
224        body = self.document_body
225        if not body:
226            body = parent
227        return body.get_annotation_end(name=name)
228
229    def get_annotated(
230        self,
231        as_text: bool = False,
232        no_header: bool = True,
233        clean: bool = True,
234    ) -> Element | list | str | None:
235        """Returns the annotated content from an annotation.
236
237        If no content exists (single position annotation or annotation-end not
238        found), returns [] (or "" if text flag is True).
239        If as_text is True: returns the text content.
240        If clean is True: suppress unwanted tags (deletions marks, ...)
241        If no_header is True: existing text:h are changed in text:p
242        By default: returns a list of odf_element, cleaned and without headers.
243
244        Arguments:
245
246            as_text -- boolean
247
248            clean -- boolean
249
250            no_header -- boolean
251
252        Return: list or Element or text or None
253        """
254        end = self.end
255        if end is None:
256            if as_text:
257                return ""
258            return None
259        body = self.document_body
260        if not body:
261            body = self.root
262        return body.get_between(
263            self, end, as_text=as_text, no_header=no_header, clean=clean
264        )
265
266    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
267        """Delete the given element from the XML tree. If no element is given,
268        "self" is deleted. The XML library may allow to continue to use an
269        element now "orphan" as long as you have a reference to it.
270
271        For Annotation : delete the annotation-end tag if exists.
272
273        Arguments:
274
275            child -- Element or None
276        """
277        if child is not None:  # act like normal delete
278            super().delete(child)
279            return
280        end = self.end
281        if end:
282            end.delete()
283        # act like normal delete
284        super().delete()
285
286    def check_validity(self) -> None:
287        if not self.note_body:
288            raise ValueError("Annotation must have a body")
289        if not self.dc_creator:
290            raise ValueError("Annotation must have a creator")
291        if not self.dc_date:
292            self.dc_date = datetime.now()

Annotation element credited to the given creator with the given text, optionally dated (current date by default). If name not provided and some parent is provided, the name is autogenerated.

Arguments:

text -- str or odf_element

creator -- str

date -- datetime

name -- str

parent -- Element
Annotation( text_or_element: Element | str | None = None, creator: str | None = None, date: datetime.datetime | None = None, name: str | None = None, parent: Element | None = None, **kwargs: Any)
172    def __init__(
173        self,
174        text_or_element: Element | str | None = None,
175        creator: str | None = None,
176        date: datetime | None = None,
177        name: str | None = None,
178        parent: Element | None = None,
179        **kwargs: Any,
180    ) -> None:
181        # fixme : use offset
182        # TODO allow paragraph and text styles
183        super().__init__(**kwargs)
184
185        if self._do_init:
186            self.note_body = text_or_element  # type:ignore
187            if creator:
188                self.dc_creator = creator
189            if date is None:
190                date = datetime.now()
191            self.dc_date = date
192            if not name:
193                name = get_unique_office_name(parent)
194                self.name = name
note_body: str
196    @property
197    def note_body(self) -> str:
198        return self.text_content
start: Element
212    @property
213    def start(self) -> Element:
214        """Return self."""
215        return self

Return self.

end: Element | None
217    @property
218    def end(self) -> Element | None:
219        """Return the corresponding annotation-end tag or None."""
220        name = self.name
221        parent = self.parent
222        if parent is None:
223            raise ValueError("Can't find end tag: no parent available")
224        body = self.document_body
225        if not body:
226            body = parent
227        return body.get_annotation_end(name=name)

Return the corresponding annotation-end tag or None.

def get_annotated( self, as_text: bool = False, no_header: bool = True, clean: bool = True) -> Element | list | str | None:
229    def get_annotated(
230        self,
231        as_text: bool = False,
232        no_header: bool = True,
233        clean: bool = True,
234    ) -> Element | list | str | None:
235        """Returns the annotated content from an annotation.
236
237        If no content exists (single position annotation or annotation-end not
238        found), returns [] (or "" if text flag is True).
239        If as_text is True: returns the text content.
240        If clean is True: suppress unwanted tags (deletions marks, ...)
241        If no_header is True: existing text:h are changed in text:p
242        By default: returns a list of odf_element, cleaned and without headers.
243
244        Arguments:
245
246            as_text -- boolean
247
248            clean -- boolean
249
250            no_header -- boolean
251
252        Return: list or Element or text or None
253        """
254        end = self.end
255        if end is None:
256            if as_text:
257                return ""
258            return None
259        body = self.document_body
260        if not body:
261            body = self.root
262        return body.get_between(
263            self, end, as_text=as_text, no_header=no_header, clean=clean
264        )

Returns the annotated content from an annotation.

If no content exists (single position annotation or annotation-end not found), returns [] (or "" if text flag is True). If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of odf_element, cleaned and without headers.

Arguments:

as_text -- boolean

clean -- boolean

no_header -- boolean

Return: list or Element or text or None

def delete( self, child: Element | None = None, keep_tail: bool = True) -> None:
266    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
267        """Delete the given element from the XML tree. If no element is given,
268        "self" is deleted. The XML library may allow to continue to use an
269        element now "orphan" as long as you have a reference to it.
270
271        For Annotation : delete the annotation-end tag if exists.
272
273        Arguments:
274
275            child -- Element or None
276        """
277        if child is not None:  # act like normal delete
278            super().delete(child)
279            return
280        end = self.end
281        if end:
282            end.delete()
283        # act like normal delete
284        super().delete()

Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.

For Annotation : delete the annotation-end tag if exists.

Arguments:

child -- Element or None
def check_validity(self) -> None:
286    def check_validity(self) -> None:
287        if not self.note_body:
288            raise ValueError("Annotation must have a body")
289        if not self.dc_creator:
290            raise ValueError("Annotation must have a creator")
291        if not self.dc_date:
292            self.dc_date = datetime.now()
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
note_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class AnnotationEnd(odfdo.Element):
298class AnnotationEnd(Element):
299    """AnnotationEnd: the "office:annotation-end" element may be used to
300    define the end of a text range of document content that spans element
301    boundaries. In that case, an "office:annotation" element shall precede
302    the "office:annotation-end" element. Both elements shall have the same
303    value for their office:name attribute. The "office:annotation-end" element
304    shall be preceded by an "office:annotation" element that has the same
305    value for its office:name attribute as the "office:annotation-end"
306    element. An "office:annotation-end" element without a preceding
307    "office:annotation" element that has the same name assigned is ignored.
308    """
309
310    _tag = "office:annotation-end"
311    _properties = (PropDef("name", "office:name"),)
312
313    def __init__(
314        self,
315        annotation: Element | None = None,
316        name: str | None = None,
317        **kwargs: Any,
318    ) -> None:
319        """Initialize an AnnotationEnd element. Either annotation or name must be
320        provided to have proper reference for the annotation-end.
321
322        Arguments:
323
324            annotation -- odf_annotation element
325
326            name -- str
327        """
328        # fixme : use offset
329        # TODO allow paragraph and text styles
330        super().__init__(**kwargs)
331        if self._do_init:
332            if annotation:
333                name = annotation.name  # type: ignore
334            if not name:
335                raise ValueError("Annotation-end must have a name")
336            self.name = name
337
338    @property
339    def start(self) -> Element | None:
340        """Return the corresponding annotation starting tag or None."""
341        name = self.name
342        parent = self.parent
343        if parent is None:
344            raise ValueError("Can't find start tag: no parent available")
345        body = self.document_body
346        if not body:
347            body = parent
348        return body.get_annotation(name=name)
349
350    @property
351    def end(self) -> Element:
352        """Return self."""
353        return self

AnnotationEnd: the "office:annotation-end" element may be used to define the end of a text range of document content that spans element boundaries. In that case, an "office:annotation" element shall precede the "office:annotation-end" element. Both elements shall have the same value for their office:name attribute. The "office:annotation-end" element shall be preceded by an "office:annotation" element that has the same value for its office:name attribute as the "office:annotation-end" element. An "office:annotation-end" element without a preceding "office:annotation" element that has the same name assigned is ignored.

AnnotationEnd( annotation: Element | None = None, name: str | None = None, **kwargs: Any)
313    def __init__(
314        self,
315        annotation: Element | None = None,
316        name: str | None = None,
317        **kwargs: Any,
318    ) -> None:
319        """Initialize an AnnotationEnd element. Either annotation or name must be
320        provided to have proper reference for the annotation-end.
321
322        Arguments:
323
324            annotation -- odf_annotation element
325
326            name -- str
327        """
328        # fixme : use offset
329        # TODO allow paragraph and text styles
330        super().__init__(**kwargs)
331        if self._do_init:
332            if annotation:
333                name = annotation.name  # type: ignore
334            if not name:
335                raise ValueError("Annotation-end must have a name")
336            self.name = name

Initialize an AnnotationEnd element. Either annotation or name must be provided to have proper reference for the annotation-end.

Arguments:

annotation -- odf_annotation element

name -- str
start: Element | None
338    @property
339    def start(self) -> Element | None:
340        """Return the corresponding annotation starting tag or None."""
341        name = self.name
342        parent = self.parent
343        if parent is None:
344            raise ValueError("Can't find start tag: no parent available")
345        body = self.document_body
346        if not body:
347            body = parent
348        return body.get_annotation(name=name)

Return the corresponding annotation starting tag or None.

end: Element
350    @property
351    def end(self) -> Element:
352        """Return self."""
353        return self

Return self.

name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class BackgroundImage(odfdo.Style, odfdo.DrawImage):
 999class BackgroundImage(Style, DrawImage):
1000    _tag = "style:background-image"
1001    _properties: tuple[PropDef, ...] = (
1002        PropDef("name", "style:name"),
1003        PropDef("display_name", "style:display-name"),
1004        PropDef("svg_font_family", "svg:font-family"),
1005        PropDef("font_family_generic", "style:font-family-generic"),
1006        PropDef("font_pitch", "style:font-pitch"),
1007        PropDef("position", "style:position", "background-image"),
1008        PropDef("repeat", "style:repeat", "background-image"),
1009        PropDef("opacity", "draw:opacity", "background-image"),
1010        PropDef("filter", "style:filter-name", "background-image"),
1011        PropDef("text_style", "text:style-name"),
1012    )
1013
1014    def __init__(
1015        self,
1016        name: str | None = None,
1017        display_name: str | None = None,
1018        position: str | None = None,
1019        repeat: str | None = None,
1020        opacity: str | None = None,
1021        filter: str | None = None,  # noqa: A002
1022        # Every other property
1023        **kwargs: Any,
1024    ):
1025        kwargs["family"] = "background-image"
1026        super().__init__(**kwargs)
1027        if self._do_init:
1028            kwargs.pop("tag", None)
1029            kwargs.pop("tag_or_elem", None)
1030            self.family = "background-image"
1031            if name:
1032                self.name = name
1033            if display_name:
1034                self.display_name = display_name
1035            if position:
1036                self.position = position
1037            if repeat:
1038                self.position = repeat
1039            if opacity:
1040                self.position = opacity
1041            if filter:
1042                self.position = filter
1043            # Every other properties
1044            for prop in BackgroundImage._properties:
1045                if prop.name in kwargs:
1046                    self.set_style_attribute(prop.attr, kwargs[prop.name])

Style class for all these tags:

'style:style' 'number:date-style', 'number:number-style', 'number:percentage-style', 'number:time-style' 'style:font-face', 'style:master-page', 'style:page-layout', 'style:presentation-page-layout', 'text:list-style', 'text:outline-style', 'style:tab-stops', ...

BackgroundImage( name: str | None = None, display_name: str | None = None, position: str | None = None, repeat: str | None = None, opacity: str | None = None, filter: str | None = None, **kwargs: Any)
1014    def __init__(
1015        self,
1016        name: str | None = None,
1017        display_name: str | None = None,
1018        position: str | None = None,
1019        repeat: str | None = None,
1020        opacity: str | None = None,
1021        filter: str | None = None,  # noqa: A002
1022        # Every other property
1023        **kwargs: Any,
1024    ):
1025        kwargs["family"] = "background-image"
1026        super().__init__(**kwargs)
1027        if self._do_init:
1028            kwargs.pop("tag", None)
1029            kwargs.pop("tag_or_elem", None)
1030            self.family = "background-image"
1031            if name:
1032                self.name = name
1033            if display_name:
1034                self.display_name = display_name
1035            if position:
1036                self.position = position
1037            if repeat:
1038                self.position = repeat
1039            if opacity:
1040                self.position = opacity
1041            if filter:
1042                self.position = filter
1043            # Every other properties
1044            for prop in BackgroundImage._properties:
1045                if prop.name in kwargs:
1046                    self.set_style_attribute(prop.attr, kwargs[prop.name])

Create a style of the given family. The name is not mandatory at this point but will become required when inserting in a document as a common style.

The display name is the name the user sees in an office application.

The parent_style is the name of the style this style will inherit from.

To set properties, pass them as keyword arguments. The area properties apply to is optional and defaults to the family.

Arguments:

family -- 'paragraph', 'text', 'section', 'table', 'table-column',
          'table-row', 'table-cell', 'table-page', 'chart',
          'drawing-page', 'graphic', 'presentation',
          'control', 'ruby', 'list', 'number', 'page-layout'
          'font-face', or 'master-page'

name -- str

display_name -- str

parent_style -- str

area -- str

'text' Properties:

italic -- bool

bold -- bool

'paragraph' Properties:

master_page -- str

'master-page' Properties:

page_layout -- str

next_style -- str

'table-cell' Properties:

border, border_top, border_right, border_bottom, border_left -- str,
e.g. "0.002cm solid #000000" or 'none'

padding, padding_top, padding_right, padding_bottom, padding_left -- str,
e.g. "0.002cm" or 'none'

shadow -- str, e.g. "#808080 0.176cm 0.176cm"

'table-row' Properties:

height -- str, e.g. '5cm'

use_optimal_height -- bool

'table-column' Properties:

width -- str, e.g. '5cm'

break_before -- 'page', 'column' or 'auto'

break_after -- 'page', 'column' or 'auto'
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
display_name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
svg_font_family: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
font_family_generic: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
font_pitch: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
position: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
repeat: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
opacity: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
filter: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
text_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Style
family
get_properties
set_properties
del_properties
set_background
get_level_style
set_level_style
get_header_style
set_header_style
get_page_header
set_page_header
set_font
page_layout
next_style
parent_style
master_page
style_type
leader_style
leader_text
style_position
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
DrawImage
url
type
show
actuate
filter_name
class Bookmark(odfdo.Element):
31class Bookmark(Element):
32    """
33    Bookmark class for ODF "text:bookmark"
34
35    Arguments:
36
37        name -- str
38    """
39
40    _tag = "text:bookmark"
41    _properties = (PropDef("name", "text:name"),)
42
43    def __init__(self, name: str = "", **kwargs: Any) -> None:
44        super().__init__(**kwargs)
45        if self._do_init:
46            self.name = name

Bookmark class for ODF "text:bookmark"

Arguments:

name -- str
Bookmark(name: str = '', **kwargs: Any)
43    def __init__(self, name: str = "", **kwargs: Any) -> None:
44        super().__init__(**kwargs)
45        if self._do_init:
46            self.name = name
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class BookmarkEnd(odfdo.Element):
73class BookmarkEnd(Element):
74    """
75    BookmarkEnd class for ODF "text:bookmark-end"
76
77    Arguments:
78
79        name -- str
80    """
81
82    _tag = "text:bookmark-end"
83    _properties = (PropDef("name", "text:name"),)
84
85    def __init__(self, name: str = "", **kwargs: Any) -> None:
86        super().__init__(**kwargs)
87        if self._do_init:
88            self.name = name

BookmarkEnd class for ODF "text:bookmark-end"

Arguments:

name -- str
BookmarkEnd(name: str = '', **kwargs: Any)
85    def __init__(self, name: str = "", **kwargs: Any) -> None:
86        super().__init__(**kwargs)
87        if self._do_init:
88            self.name = name
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class BookmarkStart(odfdo.Element):
52class BookmarkStart(Element):
53    """
54    BookmarkStart class for ODF "text:bookmark-start"
55
56    Arguments:
57
58        name -- str
59    """
60
61    _tag = "text:bookmark-start"
62    _properties = (PropDef("name", "text:name"),)
63
64    def __init__(self, name: str = "", **kwargs: Any) -> None:
65        super().__init__(**kwargs)
66        if self._do_init:
67            self.name = name

BookmarkStart class for ODF "text:bookmark-start"

Arguments:

name -- str
BookmarkStart(name: str = '', **kwargs: Any)
64    def __init__(self, name: str = "", **kwargs: Any) -> None:
65        super().__init__(**kwargs)
66        if self._do_init:
67            self.name = name
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Cell(odfdo.ElementTyped):
 44class Cell(ElementTyped):
 45    """ "table:table-cell" table cell element."""
 46
 47    _tag = "table:table-cell"
 48    _caching = True
 49
 50    def __init__(
 51        self,
 52        value: Any = None,
 53        text: str | None = None,
 54        cell_type: str | None = None,
 55        currency: str | None = None,
 56        formula: str | None = None,
 57        repeated: int | None = None,
 58        style: str | None = None,
 59        **kwargs: Any,
 60    ) -> None:
 61        """Create a cell element containing the given value. The textual
 62        representation is automatically formatted but can be provided. Cell
 63        type can be deduced as well, unless the number is a percentage or
 64        currency. If cell type is "currency", the currency must be given.
 65        The cell can be repeated on the given number of columns.
 66
 67        Arguments:
 68
 69            value -- bool, int, float, Decimal, date, datetime, str,
 70                     timedelta
 71
 72            text -- str
 73
 74            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
 75                         'string' or 'time'
 76
 77            currency -- three-letter str
 78
 79            repeated -- int
 80
 81            style -- str
 82        """
 83        super().__init__(**kwargs)
 84        self.x = None
 85        self.y = None
 86        if self._do_init:
 87            self.set_value(
 88                value,
 89                text=text,
 90                cell_type=cell_type,
 91                currency=currency,
 92                formula=formula,
 93            )
 94            if repeated and repeated > 1:
 95                self.repeated = repeated
 96            if style is not None:
 97                self.style = style
 98
 99    def __repr__(self) -> str:
100        return f"<{self.__class__.__name__} x={self.x} y={self.y}>"
101
102    @property
103    def clone(self) -> Cell:
104        clone = Element.clone.fget(self)  # type: ignore
105        clone.y = self.y
106        clone.x = self.x
107        if hasattr(self, "_tmap"):
108            if hasattr(self, "_rmap"):
109                clone._rmap = self._rmap[:]
110            clone._tmap = self._tmap[:]
111            clone._cmap = self._cmap[:]
112        return clone
113
114    @property
115    def value(
116        self,
117    ) -> str | bool | int | Float | Decimal | date | datetime | timedelta | None:
118        """Set / get the value of the cell. The type is read from the
119        'office:value-type' attribute of the cell. When setting the value,
120        the type of the value will determine the new value_type of the cell.
121
122        Warning: use this method for boolean, float or string only.
123        """
124        value_type = self.get_attribute_string("office:value-type")
125        if value_type == "boolean":
126            return self.get_attribute("office:boolean-value")
127        if value_type in {"float", "percentage", "currency"}:
128            value_decimal = Decimal(str(self.get_attribute_string("office:value")))
129            # Return 3 instead of 3.0 if possible
130            if int(value_decimal) == value_decimal:
131                return int(value_decimal)
132            return value_decimal
133        if value_type == "date":
134            value_str = str(self.get_attribute_string("office:date-value"))
135            if "T" in value_str:
136                return DateTime.decode(value_str)
137            return Date.decode(value_str)
138        if value_type == "time":
139            return Duration.decode(str(self.get_attribute_string("office:time-value")))
140        if value_type == "string":
141            value = self.get_attribute_string("office:string-value")
142            if value is not None:
143                return value
144            value_list = []
145            for para in self.get_elements("text:p"):
146                value_list.append(para.text_recursive)
147            return "\n".join(value_list)
148        return None
149
150    @value.setter
151    def value(self, value: str | bytes | bool | int | Float | Decimal | None) -> None:
152        self.clear()
153        if value is None:
154            return
155        if isinstance(value, (str, bytes)):
156            if isinstance(value, bytes):
157                value = bytes_to_str(value)
158            self.set_attribute("office:value-type", "string")
159            self.set_attribute("office:string-value", value)
160            self.text = value
161            return
162        if value is True or value is False:
163            self.set_attribute("office:value-type", "boolean")
164            value_bool = Boolean.encode(value)
165            self.set_attribute("office:boolean-value", value_bool)
166            self.text = value_bool
167            return
168        if isinstance(value, (int, Float, Decimal)):
169            self.set_attribute("office:value-type", "float")
170            value_str = str(value)
171            self.set_attribute("office:value", value_str)
172            self.text = value_str
173            return
174        raise TypeError(f"Unknown value type, try with set_value() : {value!r}")
175
176    @property
177    def float(self) -> Float:
178        """Set / get the value of the cell as a float (or 0.0)."""
179        for tag in ("office:value", "office:string-value", "office:boolean-value"):
180            read_attr = self.get_attribute(tag)
181            if isinstance(read_attr, str):
182                with contextlib.suppress(ValueError, TypeError):
183                    return Float(read_attr)
184        return 0.0
185
186    @float.setter
187    def float(self, value: str | Float | int | Decimal) -> None:
188        try:
189            value_float = Float(value)
190        except (ValueError, TypeError):
191            value_float = 0.0
192        value_str = str(value_float)
193        self.clear()
194        self.set_attribute("office:value", value_str)
195        self.set_attribute("office:value-type", "float")
196        self.text = value_str
197
198    @property
199    def string(self) -> str:
200        """Set / get the value of the cell as a string (or '')."""
201        value = self.get_attribute_string("office:string-value")
202        if isinstance(value, str):
203            return value
204        return ""
205
206    @string.setter
207    def string(
208        self,
209        value: str | bytes | int | Float | Decimal | bool | None,  # type: ignore
210    ) -> None:
211        self.clear()
212        if value is None:
213            value_str = ""
214        else:
215            value_str = str(value)
216        self.set_attribute("office:value-type", "string")
217        self.set_attribute("office:string-value", value_str)
218        self.text = value_str
219
220    def set_value(
221        self,
222        value: (
223            str  # type: ignore
224            | bytes
225            | Float
226            | int
227            | Decimal
228            | bool
229            | datetime
230            | date
231            | timedelta
232            | None
233        ),
234        text: str | None = None,
235        cell_type: str | None = None,
236        currency: str | None = None,
237        formula: str | None = None,
238    ) -> None:
239        """Set the cell state from the Python value type.
240
241        Text is how the cell is displayed. Cell type is guessed,
242        unless provided.
243
244        For monetary values, provide the name of the currency.
245
246        Arguments:
247
248            value -- Python type
249
250            text -- str
251
252            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
253                        'currency' or 'percentage'
254
255            currency -- str
256        """
257        self.clear()
258        text = self.set_value_and_type(
259            value=value,
260            text=text,
261            value_type=cell_type,
262            currency=currency,
263        )
264        if text is not None:
265            self.text_content = text
266        if formula is not None:
267            self.formula = formula
268
269    @property
270    def type(self) -> str | None:
271        """Get / set the type of the cell: boolean, float, date, string
272        or time.
273
274        Return: str | None
275        """
276        return self.get_attribute_string("office:value-type")
277
278    @type.setter
279    def type(self, cell_type: str) -> None:
280        self.set_attribute("office:value-type", cell_type)
281
282    @property
283    def currency(self) -> str | None:
284        """Get / set the currency used for monetary values.
285
286        Return: str | None
287        """
288        return self.get_attribute_string("office:currency")
289
290    @currency.setter
291    def currency(self, currency: str) -> None:
292        self.set_attribute("office:currency", currency)
293
294    def _set_repeated(self, repeated: int | None) -> None:
295        """Internal only. Set the numnber of times the cell is repeated, or
296        None to delete. Without changing cache.
297        """
298        if repeated is None or repeated < 2:
299            with contextlib.suppress(KeyError):
300                self.del_attribute("table:number-columns-repeated")
301            return
302        self.set_attribute("table:number-columns-repeated", str(repeated))
303
304    @property
305    def repeated(self) -> int | None:
306        """Get / set the number of times the cell is repeated.
307
308        Always None when using the table API.
309
310        Return: int or None
311        """
312        repeated = self.get_attribute("table:number-columns-repeated")
313        if repeated is None:
314            return None
315        return int(repeated)
316
317    @repeated.setter
318    def repeated(self, repeated: int | None) -> None:
319        self._set_repeated(repeated)
320        # update cache
321        child: Element = self
322        while True:
323            # look for Row, parent may be group of rows
324            upper = child.parent
325            if not upper:
326                # lonely cell
327                return
328            # parent may be group of rows, not table
329            if isinstance(upper, Element) and upper._tag == "table:table-row":
330                break
331            child = upper
332        # fixme : need to optimize this
333        if isinstance(upper, Element) and upper._tag == "table:table-row":
334            upper._compute_row_cache()
335
336    @property
337    def style(self) -> str | None:
338        """Get / set the style of the cell itself.
339
340        Return: str | None
341        """
342        return self.get_attribute_string("table:style-name")
343
344    @style.setter
345    def style(self, style: str | Element) -> None:
346        self.set_style_attribute("table:style-name", style)
347
348    @property
349    def formula(self) -> str | None:
350        """Get / set the formula of the cell, or None if undefined.
351
352        The formula is not interpreted in any way.
353
354        Return: str | None
355        """
356        return self.get_attribute_string("table:formula")
357
358    @formula.setter
359    def formula(self, formula: str | None) -> None:
360        self.set_attribute("table:formula", formula)
361
362    def is_empty(self, aggressive: bool = False) -> bool:
363        """Return whether the cell has no value or the value evaluates
364        to False (empty string), and no style.
365
366        If aggressive is True, empty cells with style are considered empty.
367
368        Arguments:
369
370            aggressive -- bool
371
372        Return: bool
373        """
374        if self.value is not None or self.children or self.is_spanned():
375            return False
376        if not aggressive and self.style is not None:
377            return False
378        return True
379
380    def is_spanned(self) -> bool:
381        """Return whether the cell is spanned over several cells.
382
383        Returns: True | False
384        """
385        if self.tag == "table:covered-table-cell":
386            return True
387        if self.get_attribute("table:number-columns-spanned") is not None:
388            return True
389        if self.get_attribute("table:number-rows-spanned") is not None:
390            return True
391        return False
392
393    _is_spanned = is_spanned  # compatibility

"table:table-cell" table cell element.

Cell( value: Any = None, text: str | None = None, cell_type: str | None = None, currency: str | None = None, formula: str | None = None, repeated: int | None = None, style: str | None = None, **kwargs: Any)
50    def __init__(
51        self,
52        value: Any = None,
53        text: str | None = None,
54        cell_type: str | None = None,
55        currency: str | None = None,
56        formula: str | None = None,
57        repeated: int | None = None,
58        style: str | None = None,
59        **kwargs: Any,
60    ) -> None:
61        """Create a cell element containing the given value. The textual
62        representation is automatically formatted but can be provided. Cell
63        type can be deduced as well, unless the number is a percentage or
64        currency. If cell type is "currency", the currency must be given.
65        The cell can be repeated on the given number of columns.
66
67        Arguments:
68
69            value -- bool, int, float, Decimal, date, datetime, str,
70                     timedelta
71
72            text -- str
73
74            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
75                         'string' or 'time'
76
77            currency -- three-letter str
78
79            repeated -- int
80
81            style -- str
82        """
83        super().__init__(**kwargs)
84        self.x = None
85        self.y = None
86        if self._do_init:
87            self.set_value(
88                value,
89                text=text,
90                cell_type=cell_type,
91                currency=currency,
92                formula=formula,
93            )
94            if repeated and repeated > 1:
95                self.repeated = repeated
96            if style is not None:
97                self.style = style

Create a cell element containing the given value. The textual representation is automatically formatted but can be provided. Cell type can be deduced as well, unless the number is a percentage or currency. If cell type is "currency", the currency must be given. The cell can be repeated on the given number of columns.

Arguments:

value -- bool, int, float, Decimal, date, datetime, str,
         timedelta

text -- str

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
             'string' or 'time'

currency -- three-letter str

repeated -- int

style -- str
x
y
clone: Cell
102    @property
103    def clone(self) -> Cell:
104        clone = Element.clone.fget(self)  # type: ignore
105        clone.y = self.y
106        clone.x = self.x
107        if hasattr(self, "_tmap"):
108            if hasattr(self, "_rmap"):
109                clone._rmap = self._rmap[:]
110            clone._tmap = self._tmap[:]
111            clone._cmap = self._cmap[:]
112        return clone
value: str | bool | int | float | decimal.Decimal | datetime.date | datetime.datetime | datetime.timedelta | None
114    @property
115    def value(
116        self,
117    ) -> str | bool | int | Float | Decimal | date | datetime | timedelta | None:
118        """Set / get the value of the cell. The type is read from the
119        'office:value-type' attribute of the cell. When setting the value,
120        the type of the value will determine the new value_type of the cell.
121
122        Warning: use this method for boolean, float or string only.
123        """
124        value_type = self.get_attribute_string("office:value-type")
125        if value_type == "boolean":
126            return self.get_attribute("office:boolean-value")
127        if value_type in {"float", "percentage", "currency"}:
128            value_decimal = Decimal(str(self.get_attribute_string("office:value")))
129            # Return 3 instead of 3.0 if possible
130            if int(value_decimal) == value_decimal:
131                return int(value_decimal)
132            return value_decimal
133        if value_type == "date":
134            value_str = str(self.get_attribute_string("office:date-value"))
135            if "T" in value_str:
136                return DateTime.decode(value_str)
137            return Date.decode(value_str)
138        if value_type == "time":
139            return Duration.decode(str(self.get_attribute_string("office:time-value")))
140        if value_type == "string":
141            value = self.get_attribute_string("office:string-value")
142            if value is not None:
143                return value
144            value_list = []
145            for para in self.get_elements("text:p"):
146                value_list.append(para.text_recursive)
147            return "\n".join(value_list)
148        return None

Set / get the value of the cell. The type is read from the 'office:value-type' attribute of the cell. When setting the value, the type of the value will determine the new value_type of the cell.

Warning: use this method for boolean, float or string only.

float: float
176    @property
177    def float(self) -> Float:
178        """Set / get the value of the cell as a float (or 0.0)."""
179        for tag in ("office:value", "office:string-value", "office:boolean-value"):
180            read_attr = self.get_attribute(tag)
181            if isinstance(read_attr, str):
182                with contextlib.suppress(ValueError, TypeError):
183                    return Float(read_attr)
184        return 0.0

Set / get the value of the cell as a float (or 0.0).

string: str
198    @property
199    def string(self) -> str:
200        """Set / get the value of the cell as a string (or '')."""
201        value = self.get_attribute_string("office:string-value")
202        if isinstance(value, str):
203            return value
204        return ""

Set / get the value of the cell as a string (or '').

def set_value( self, value: str | bytes | float | int | decimal.Decimal | bool | datetime.datetime | datetime.date | datetime.timedelta | None, text: str | None = None, cell_type: str | None = None, currency: str | None = None, formula: str | None = None) -> None:
220    def set_value(
221        self,
222        value: (
223            str  # type: ignore
224            | bytes
225            | Float
226            | int
227            | Decimal
228            | bool
229            | datetime
230            | date
231            | timedelta
232            | None
233        ),
234        text: str | None = None,
235        cell_type: str | None = None,
236        currency: str | None = None,
237        formula: str | None = None,
238    ) -> None:
239        """Set the cell state from the Python value type.
240
241        Text is how the cell is displayed. Cell type is guessed,
242        unless provided.
243
244        For monetary values, provide the name of the currency.
245
246        Arguments:
247
248            value -- Python type
249
250            text -- str
251
252            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
253                        'currency' or 'percentage'
254
255            currency -- str
256        """
257        self.clear()
258        text = self.set_value_and_type(
259            value=value,
260            text=text,
261            value_type=cell_type,
262            currency=currency,
263        )
264        if text is not None:
265            self.text_content = text
266        if formula is not None:
267            self.formula = formula

Set the cell state from the Python value type.

Text is how the cell is displayed. Cell type is guessed, unless provided.

For monetary values, provide the name of the currency.

Arguments:

value -- Python type

text -- str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
            'currency' or 'percentage'

currency -- str
type: str | None
269    @property
270    def type(self) -> str | None:
271        """Get / set the type of the cell: boolean, float, date, string
272        or time.
273
274        Return: str | None
275        """
276        return self.get_attribute_string("office:value-type")

Get / set the type of the cell: boolean, float, date, string or time.

Return: str | None

currency: str | None
282    @property
283    def currency(self) -> str | None:
284        """Get / set the currency used for monetary values.
285
286        Return: str | None
287        """
288        return self.get_attribute_string("office:currency")

Get / set the currency used for monetary values.

Return: str | None

repeated: int | None
304    @property
305    def repeated(self) -> int | None:
306        """Get / set the number of times the cell is repeated.
307
308        Always None when using the table API.
309
310        Return: int or None
311        """
312        repeated = self.get_attribute("table:number-columns-repeated")
313        if repeated is None:
314            return None
315        return int(repeated)

Get / set the number of times the cell is repeated.

Always None when using the table API.

Return: int or None

style: str | None
336    @property
337    def style(self) -> str | None:
338        """Get / set the style of the cell itself.
339
340        Return: str | None
341        """
342        return self.get_attribute_string("table:style-name")

Get / set the style of the cell itself.

Return: str | None

formula: str | None
348    @property
349    def formula(self) -> str | None:
350        """Get / set the formula of the cell, or None if undefined.
351
352        The formula is not interpreted in any way.
353
354        Return: str | None
355        """
356        return self.get_attribute_string("table:formula")

Get / set the formula of the cell, or None if undefined.

The formula is not interpreted in any way.

Return: str | None

def is_empty(self, aggressive: bool = False) -> bool:
362    def is_empty(self, aggressive: bool = False) -> bool:
363        """Return whether the cell has no value or the value evaluates
364        to False (empty string), and no style.
365
366        If aggressive is True, empty cells with style are considered empty.
367
368        Arguments:
369
370            aggressive -- bool
371
372        Return: bool
373        """
374        if self.value is not None or self.children or self.is_spanned():
375            return False
376        if not aggressive and self.style is not None:
377            return False
378        return True

Return whether the cell has no value or the value evaluates to False (empty string), and no style.

If aggressive is True, empty cells with style are considered empty.

Arguments:

aggressive -- bool

Return: bool

def is_spanned(self) -> bool:
380    def is_spanned(self) -> bool:
381        """Return whether the cell is spanned over several cells.
382
383        Returns: True | False
384        """
385        if self.tag == "table:covered-table-cell":
386            return True
387        if self.get_attribute("table:number-columns-spanned") is not None:
388            return True
389        if self.get_attribute("table:number-rows-spanned") is not None:
390            return True
391        return False

Return whether the cell is spanned over several cells.

Returns: True | False

Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ChangeInfo(odfdo.Element):
 37class ChangeInfo(Element):
 38    """The "office:change-info" element represents who made a change and when.
 39    It may also contain a comment (one or more Paragrah "text:p" elements)
 40    on the change.
 41
 42    The comments available in the ChangeInfo are available through:
 43      - get_paragraphs and get_paragraph methods for actual Paragraph.
 44      - get_comments for a plain text version
 45
 46      Arguments:
 47
 48         creator -- str (or None)
 49
 50         date -- datetime (or None)
 51    """
 52
 53    _tag = "office:change-info"
 54
 55    def __init__(
 56        self,
 57        creator: str | None = None,
 58        date: datetime | None = None,
 59        **kwargs: Any,
 60    ) -> None:
 61        super().__init__(**kwargs)
 62        if self._do_init:
 63            self.set_dc_creator(creator)
 64            self.set_dc_date(date)
 65
 66    def set_dc_creator(self, creator: str | None = None) -> None:
 67        """Set the creator of the change. Default for creator is 'Unknown'.
 68
 69        Arguments:
 70
 71            creator -- str (or None)
 72        """
 73        element = self.get_element("dc:creator")
 74        if element is None:
 75            element = Element.from_tag("dc:creator")
 76            self.insert(element, xmlposition=FIRST_CHILD)
 77        if not creator:
 78            creator = "Unknown"
 79        element.text = creator
 80
 81    def set_dc_date(self, date: datetime | None = None) -> None:
 82        """Set the date of the change. If date is None, use current time.
 83
 84        Arguments:
 85
 86            date -- datetime (or None)
 87        """
 88        if date is None:
 89            date = datetime.now()
 90        dcdate = DateTime.encode(date)
 91        element = self.get_element("dc:date")
 92        if element is None:
 93            element = Element.from_tag("dc:date")
 94            self.insert(element, xmlposition=LAST_CHILD)
 95        element.text = dcdate
 96
 97    def get_comments(self, joined: bool = True) -> str | list[str]:
 98        """Get text content of the comments. If joined is True (default), the
 99        text of different paragraphs is concatenated, else a list of strings,
100        one per paragraph, is returned.
101
102        Arguments:
103
104            joined -- boolean (default is True)
105
106        Return: str or list of str.
107        """
108        content = self.get_paragraphs()
109        if content is None:
110            content = []
111        text = [para.get_formatted_text(simple=True) for para in content]  # type: ignore
112        if joined:
113            return "\n".join(text)
114        return text
115
116    def set_comments(self, text: str = "", replace: bool = True) -> None:
117        """Set the text content of the comments. If replace is True (default),
118        the new text replace old comments, else it is added at the end.
119
120        Arguments:
121
122            text -- str
123
124            replace -- boolean
125        """
126        if replace:
127            for para in self.get_paragraphs():
128                self.delete(para)
129        para = Paragraph()
130        para.append_plain_text(text)
131        self.insert(para, xmlposition=LAST_CHILD)

The "office:change-info" element represents who made a change and when. It may also contain a comment (one or more Paragrah "text:p" elements) on the change.

The comments available in the ChangeInfo are available through:

  • get_paragraphs and get_paragraph methods for actual Paragraph.
  • get_comments for a plain text version

Arguments:

 creator -- str (or None)

 date -- datetime (or None)
ChangeInfo( creator: str | None = None, date: datetime.datetime | None = None, **kwargs: Any)
55    def __init__(
56        self,
57        creator: str | None = None,
58        date: datetime | None = None,
59        **kwargs: Any,
60    ) -> None:
61        super().__init__(**kwargs)
62        if self._do_init:
63            self.set_dc_creator(creator)
64            self.set_dc_date(date)
def set_dc_creator(self, creator: str | None = None) -> None:
66    def set_dc_creator(self, creator: str | None = None) -> None:
67        """Set the creator of the change. Default for creator is 'Unknown'.
68
69        Arguments:
70
71            creator -- str (or None)
72        """
73        element = self.get_element("dc:creator")
74        if element is None:
75            element = Element.from_tag("dc:creator")
76            self.insert(element, xmlposition=FIRST_CHILD)
77        if not creator:
78            creator = "Unknown"
79        element.text = creator

Set the creator of the change. Default for creator is 'Unknown'.

Arguments:

creator -- str (or None)
def set_dc_date(self, date: datetime.datetime | None = None) -> None:
81    def set_dc_date(self, date: datetime | None = None) -> None:
82        """Set the date of the change. If date is None, use current time.
83
84        Arguments:
85
86            date -- datetime (or None)
87        """
88        if date is None:
89            date = datetime.now()
90        dcdate = DateTime.encode(date)
91        element = self.get_element("dc:date")
92        if element is None:
93            element = Element.from_tag("dc:date")
94            self.insert(element, xmlposition=LAST_CHILD)
95        element.text = dcdate

Set the date of the change. If date is None, use current time.

Arguments:

date -- datetime (or None)
def get_comments(self, joined: bool = True) -> str | list[str]:
 97    def get_comments(self, joined: bool = True) -> str | list[str]:
 98        """Get text content of the comments. If joined is True (default), the
 99        text of different paragraphs is concatenated, else a list of strings,
100        one per paragraph, is returned.
101
102        Arguments:
103
104            joined -- boolean (default is True)
105
106        Return: str or list of str.
107        """
108        content = self.get_paragraphs()
109        if content is None:
110            content = []
111        text = [para.get_formatted_text(simple=True) for para in content]  # type: ignore
112        if joined:
113            return "\n".join(text)
114        return text

Get text content of the comments. If joined is True (default), the text of different paragraphs is concatenated, else a list of strings, one per paragraph, is returned.

Arguments:

joined -- boolean (default is True)

Return: str or list of str.

def set_comments(self, text: str = '', replace: bool = True) -> None:
116    def set_comments(self, text: str = "", replace: bool = True) -> None:
117        """Set the text content of the comments. If replace is True (default),
118        the new text replace old comments, else it is added at the end.
119
120        Arguments:
121
122            text -- str
123
124            replace -- boolean
125        """
126        if replace:
127            for para in self.get_paragraphs():
128                self.delete(para)
129        para = Paragraph()
130        para.append_plain_text(text)
131        self.insert(para, xmlposition=LAST_CHILD)

Set the text content of the comments. If replace is True (default), the new text replace old comments, else it is added at the end.

Arguments:

text -- str

replace -- boolean
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Column(odfdo.Element):
164class Column(Element):
165    """ODF table column "table:table-column" """
166
167    _tag = "table:table-column"
168    _caching = True
169
170    def __init__(
171        self,
172        default_cell_style: str | None = None,
173        repeated: int | None = None,
174        style: str | None = None,
175        **kwargs: Any,
176    ) -> None:
177        """Create a column group element of the optionally given style. Cell
178        style can be set for the whole column. If the properties apply to
179        several columns, give the number of repeated columns.
180
181        Columns don't contain cells, just style information.
182
183        You don't generally have to create columns by hand, use the Table API.
184
185        Arguments:
186
187            default_cell_style -- str
188
189            repeated -- int
190
191            style -- str
192        """
193        super().__init__(**kwargs)
194        self.x = None
195        if self._do_init:
196            if default_cell_style:
197                self.set_default_cell_style(default_cell_style)
198            if repeated and repeated > 1:
199                self.repeated = repeated
200            if style:
201                self.style = style
202
203    def __repr__(self) -> str:
204        return f"<{self.__class__.__name__} x={self.x}>"
205
206    @property
207    def clone(self) -> Column:
208        clone = Element.clone.fget(self)  # type: ignore
209        clone.x = self.x
210        if hasattr(self, "_tmap"):
211            if hasattr(self, "_rmap"):
212                clone._rmap = self._rmap[:]
213            clone._tmap = self._tmap[:]
214            clone._cmap = self._cmap[:]
215        return clone
216
217    def get_default_cell_style(self) -> str | None:
218        return self.get_attribute_string("table:default-cell-style-name")
219
220    def set_default_cell_style(self, style: Element | str) -> None:
221        self.set_style_attribute("table:default-cell-style-name", style)
222
223    def _set_repeated(self, repeated: int | None) -> None:
224        """Internal only. Set the number of times the column is repeated, or
225        None to delete it. Without changing cache.
226
227        Arguments:
228
229            repeated -- int or None
230        """
231        if repeated is None or repeated < 2:
232            with contextlib.suppress(KeyError):
233                self.del_attribute("table:number-columns-repeated")
234            return
235        self.set_attribute("table:number-columns-repeated", str(repeated))
236
237    @property
238    def repeated(self) -> int | None:
239        """Get /set the number of times the column is repeated.
240
241        Always None when using the table API.
242
243        Return: int or None
244        """
245        repeated = self.get_attribute("table:number-columns-repeated")
246        if repeated is None:
247            return None
248        return int(repeated)
249
250    @repeated.setter
251    def repeated(self, repeated: int | None) -> None:
252        self._set_repeated(repeated)
253        # update cache
254        current: Element = self
255        while True:
256            # look for Table, parent may be group of rows
257            upper = current.parent
258            if not upper:
259                # lonely column
260                return
261            # parent may be group of rows, not table
262            if isinstance(upper, Table):
263                break
264            current = upper
265        # fixme : need to optimize this
266        if isinstance(upper, Table):
267            upper._compute_table_cache()
268            if hasattr(self, "_cmap"):
269                del self._cmap[:]
270                self._cmap.extend(upper._cmap)
271            else:
272                self._cmap = upper._cmap
273
274    @property
275    def style(self) -> str | None:
276        """Get /set the style of the column itself.
277
278        Return: str
279        """
280        return self.get_attribute_string("table:style-name")
281
282    @style.setter
283    def style(self, style: str | Element) -> None:
284        self.set_style_attribute("table:style-name", style)

ODF table column "table:table-column"

Column( default_cell_style: str | None = None, repeated: int | None = None, style: str | None = None, **kwargs: Any)
170    def __init__(
171        self,
172        default_cell_style: str | None = None,
173        repeated: int | None = None,
174        style: str | None = None,
175        **kwargs: Any,
176    ) -> None:
177        """Create a column group element of the optionally given style. Cell
178        style can be set for the whole column. If the properties apply to
179        several columns, give the number of repeated columns.
180
181        Columns don't contain cells, just style information.
182
183        You don't generally have to create columns by hand, use the Table API.
184
185        Arguments:
186
187            default_cell_style -- str
188
189            repeated -- int
190
191            style -- str
192        """
193        super().__init__(**kwargs)
194        self.x = None
195        if self._do_init:
196            if default_cell_style:
197                self.set_default_cell_style(default_cell_style)
198            if repeated and repeated > 1:
199                self.repeated = repeated
200            if style:
201                self.style = style

Create a column group element of the optionally given style. Cell style can be set for the whole column. If the properties apply to several columns, give the number of repeated columns.

Columns don't contain cells, just style information.

You don't generally have to create columns by hand, use the Table API.

Arguments:

default_cell_style -- str

repeated -- int

style -- str
x
clone: Column
206    @property
207    def clone(self) -> Column:
208        clone = Element.clone.fget(self)  # type: ignore
209        clone.x = self.x
210        if hasattr(self, "_tmap"):
211            if hasattr(self, "_rmap"):
212                clone._rmap = self._rmap[:]
213            clone._tmap = self._tmap[:]
214            clone._cmap = self._cmap[:]
215        return clone
def get_default_cell_style(self) -> str | None:
217    def get_default_cell_style(self) -> str | None:
218        return self.get_attribute_string("table:default-cell-style-name")
def set_default_cell_style(self, style: Element | str) -> None:
220    def set_default_cell_style(self, style: Element | str) -> None:
221        self.set_style_attribute("table:default-cell-style-name", style)
repeated: int | None
237    @property
238    def repeated(self) -> int | None:
239        """Get /set the number of times the column is repeated.
240
241        Always None when using the table API.
242
243        Return: int or None
244        """
245        repeated = self.get_attribute("table:number-columns-repeated")
246        if repeated is None:
247            return None
248        return int(repeated)

Get /set the number of times the column is repeated.

Always None when using the table API.

Return: int or None

style: str | None
274    @property
275    def style(self) -> str | None:
276        """Get /set the style of the column itself.
277
278        Return: str
279        """
280        return self.get_attribute_string("table:style-name")

Get /set the style of the column itself.

Return: str

Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ConnectorShape(odfdo.shapes.ShapeBase):
242class ConnectorShape(ShapeBase):
243    """Create a Connector shape.
244
245    Arguments:
246
247        style -- str
248
249        text_style -- str
250
251        draw_id -- str
252
253        layer -- str
254
255        connected_shapes -- (shape, shape)
256
257        glue_points -- (point, point)
258
259        p1 -- (str, str)
260
261        p2 -- (str, str)
262    """
263
264    _tag = "draw:connector"
265    _properties: tuple[PropDef, ...] = (
266        PropDef("start_shape", "draw:start-shape"),
267        PropDef("end_shape", "draw:end-shape"),
268        PropDef("start_glue_point", "draw:start-glue-point"),
269        PropDef("end_glue_point", "draw:end-glue-point"),
270        PropDef("x1", "svg:x1"),
271        PropDef("y1", "svg:y1"),
272        PropDef("x2", "svg:x2"),
273        PropDef("y2", "svg:y2"),
274    )
275
276    def __init__(
277        self,
278        style: str | None = None,
279        text_style: str | None = None,
280        draw_id: str | None = None,
281        layer: str | None = None,
282        connected_shapes: tuple | None = None,
283        glue_points: tuple | None = None,
284        p1: tuple | None = None,
285        p2: tuple | None = None,
286        **kwargs: Any,
287    ) -> None:
288        kwargs.update(
289            {
290                "style": style,
291                "text_style": text_style,
292                "draw_id": draw_id,
293                "layer": layer,
294            }
295        )
296        super().__init__(**kwargs)
297        if self._do_init:
298            if connected_shapes:
299                self.start_shape = connected_shapes[0].draw_id
300                self.end_shape = connected_shapes[1].draw_id
301            if glue_points:
302                self.start_glue_point = glue_points[0]
303                self.end_glue_point = glue_points[1]
304            if p1:
305                self.x1 = p1[0]
306                self.y1 = p1[1]
307            if p2:
308                self.x2 = p2[0]
309                self.y2 = p2[1]

Create a Connector shape.

Arguments:

style -- str

text_style -- str

draw_id -- str

layer -- str

connected_shapes -- (shape, shape)

glue_points -- (point, point)

p1 -- (str, str)

p2 -- (str, str)
ConnectorShape( style: str | None = None, text_style: str | None = None, draw_id: str | None = None, layer: str | None = None, connected_shapes: tuple | None = None, glue_points: tuple | None = None, p1: tuple | None = None, p2: tuple | None = None, **kwargs: Any)
276    def __init__(
277        self,
278        style: str | None = None,
279        text_style: str | None = None,
280        draw_id: str | None = None,
281        layer: str | None = None,
282        connected_shapes: tuple | None = None,
283        glue_points: tuple | None = None,
284        p1: tuple | None = None,
285        p2: tuple | None = None,
286        **kwargs: Any,
287    ) -> None:
288        kwargs.update(
289            {
290                "style": style,
291                "text_style": text_style,
292                "draw_id": draw_id,
293                "layer": layer,
294            }
295        )
296        super().__init__(**kwargs)
297        if self._do_init:
298            if connected_shapes:
299                self.start_shape = connected_shapes[0].draw_id
300                self.end_shape = connected_shapes[1].draw_id
301            if glue_points:
302                self.start_glue_point = glue_points[0]
303                self.end_glue_point = glue_points[1]
304            if p1:
305                self.x1 = p1[0]
306                self.y1 = p1[1]
307            if p2:
308                self.x2 = p2[0]
309                self.y2 = p2[1]
start_shape: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
end_shape: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
start_glue_point: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
end_glue_point: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
x1: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
y1: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
x2: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
y2: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
odfdo.shapes.ShapeBase
get_formatted_text
draw_id
layer
width
height
pos_x
pos_y
presentation_class
style
text_style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.SizeMix
size
odfdo.frame.PosMix
position
class Container:
 55class Container:
 56    """Representation of the ODF file."""
 57
 58    def __init__(self, path: Path | str | io.BytesIO | None = None) -> None:
 59        self.__parts: dict[str, bytes | None] = {}
 60        self.__parts_ts: dict[str, int] = {}
 61        self.__path_like: Path | str | io.BytesIO | None = None
 62        self.__packaging: str = "zip"
 63        self.path: Path | None = None  # or Path
 64        if path:
 65            self.open(path)
 66
 67    def __repr__(self) -> str:
 68        return f"<{self.__class__.__name__} type={self.mimetype} path={self.path}>"
 69
 70    def open(self, path_or_file: Path | str | io.BytesIO) -> None:
 71        """Load the content of an ODF file."""
 72        self.__path_like = path_or_file
 73        if isinstance(path_or_file, (str, Path)):
 74            self.path = Path(path_or_file).expanduser()
 75            if not self.path.exists():
 76                raise FileNotFoundError(str(self.path))
 77            self.__path_like = self.path
 78        if (self.path or isinstance(self.__path_like, io.BytesIO)) and is_zipfile(
 79            self.__path_like  # type: ignore
 80        ):
 81            self.__packaging = "zip"
 82            return self._read_zip()
 83        if self.path:
 84            is_folder = False
 85            with contextlib.suppress(OSError):
 86                is_folder = self.path.is_dir()
 87            if is_folder:
 88                self.__packaging = "folder"
 89                return self._read_folder()
 90        raise TypeError(f"Document format not managed by odfdo: {type(path_or_file)}.")
 91
 92    def _read_zip(self) -> None:
 93        if isinstance(self.__path_like, io.BytesIO):
 94            self.__path_like.seek(0)
 95        with ZipFile(self.__path_like) as zf:  # type: ignore
 96            mimetype = bytes_to_str(zf.read("mimetype"))
 97            if mimetype not in ODF_MIMETYPES:
 98                raise ValueError(f"Document of unknown type {mimetype}")
 99            self.__parts["mimetype"] = str_to_bytes(mimetype)
100        if self.path is None:
101            if isinstance(self.__path_like, io.BytesIO):
102                self.__path_like.seek(0)
103            # read the full file at once and forget file
104            with ZipFile(self.__path_like) as zf:  # type: ignore
105                for name in zf.namelist():
106                    upath = normalize_path(name)
107                    self.__parts[upath] = zf.read(name)
108            self.__path_like = None
109
110    def _read_folder(self) -> None:
111        try:
112            mimetype, timestamp = self._get_folder_part("mimetype")
113        except OSError:
114            printwarn("Corrupted or not an OpenDocument folder (missing mimetype)")
115            mimetype = b""
116            timestamp = int(time.time())
117        if bytes_to_str(mimetype) not in ODF_MIMETYPES:
118            message = f"Document of unknown type {mimetype!r}, try with ODF Text."
119            printwarn(message)
120            self.__parts["mimetype"] = str_to_bytes(ODF_EXTENSIONS["odt"])
121            self.__parts_ts["mimetype"] = timestamp
122
123    def _parse_folder(self, folder: str) -> list[str]:
124        parts = []
125        if self.path is None:
126            raise ValueError("Document path is not defined")
127        root = self.path / folder
128        for path in root.iterdir():
129            if path.name.startswith("."):  # no hidden files
130                continue
131            relative_path = path.relative_to(self.path)
132            if path.is_file():
133                parts.append(relative_path.as_posix())
134            if path.is_dir():
135                sub_parts = self._parse_folder(str(relative_path))
136                if sub_parts:
137                    parts.extend(sub_parts)
138                else:
139                    # store leaf directories
140                    parts.append(relative_path.as_posix() + "/")
141        return parts
142
143    def _get_folder_parts(self) -> list[str]:
144        """Get the list of members in the ODF folder."""
145        return self._parse_folder("")
146
147    def _get_folder_part(self, name: str) -> tuple[bytes, int]:
148        """Get bytes of a part from the ODF folder, with timestamp."""
149        if self.path is None:
150            raise ValueError("Document path is not defined")
151        path = self.path / name
152        timestamp = int(path.stat().st_mtime)
153        if path.is_dir():
154            return (b"", timestamp)
155        return (path.read_bytes(), timestamp)
156
157    def _get_folder_part_timestamp(self, name: str) -> int:
158        if self.path is None:
159            raise ValueError("Document path is not defined")
160        path = self.path / name
161        try:
162            timestamp = int(path.stat().st_mtime)
163        except OSError:
164            timestamp = -1
165        return timestamp
166
167    def _get_zip_part(self, name: str) -> bytes | None:
168        """Get bytes of a part from the Zip ODF file. No cache."""
169        if self.path is None:
170            raise ValueError("Document path is not defined")
171        try:
172            with ZipFile(self.path) as zf:
173                upath = normalize_path(name)
174                self.__parts[upath] = zf.read(name)
175                return self.__parts[upath]
176        except BadZipfile:
177            return None
178
179    def _get_all_zip_part(self) -> None:
180        """Read all parts. No cache."""
181        if self.path is None:
182            raise ValueError("Document path is not defined")
183        try:
184            with ZipFile(self.path) as zf:
185                for name in zf.namelist():
186                    upath = normalize_path(name)
187                    self.__parts[upath] = zf.read(name)
188        except BadZipfile:
189            pass
190
191    def _save_zip(self, target: str | Path | io.BytesIO) -> None:
192        """Save a Zip ODF from the available parts."""
193        parts = self.__parts
194        with ZipFile(target, "w", compression=ZIP_DEFLATED) as filezip:
195            # Parts to save, except manifest at the end
196            part_names = list(parts.keys())
197            try:
198                part_names.remove(ODF_MANIFEST)
199            except ValueError:
200                printwarn(f"Missing '{ODF_MANIFEST}'")
201            # "Pretty-save" parts in some order
202            # mimetype requires to be first and uncompressed
203            mimetype = parts.get("mimetype")
204            if mimetype is None:
205                raise ValueError("Mimetype is not defined")
206            try:
207                filezip.writestr("mimetype", mimetype, ZIP_STORED)
208                part_names.remove("mimetype")
209            except (ValueError, KeyError):
210                printwarn("Missing 'mimetype'")
211            # XML parts
212            for path in ODF_CONTENT, ODF_META, ODF_SETTINGS, ODF_STYLES:
213                if path not in parts:
214                    printwarn(f"Missing '{path}'")
215                    continue
216                part = parts[path]
217                if part is None:
218                    continue
219                filezip.writestr(path, part)
220                part_names.remove(path)
221            # Everything else
222            for path in part_names:
223                data = parts[path]
224                if data is None:
225                    # Deleted
226                    continue
227                filezip.writestr(path, data)
228            # Manifest
229            with contextlib.suppress(KeyError):
230                part = parts[ODF_MANIFEST]
231                if part is not None:
232                    filezip.writestr(ODF_MANIFEST, part)
233
234    def _save_folder(self, folder: Path | str) -> None:
235        """Save a folder ODF from the available parts."""
236
237        def dump(part_path: str, content: bytes) -> None:
238            if part_path.endswith("/"):  # folder
239                is_folder = True
240                pure_path = PurePath(folder, part_path[:-1])
241            else:
242                is_folder = False
243                pure_path = PurePath(folder, part_path)
244            path = Path(pure_path)
245            if is_folder:
246                path.mkdir(parents=True, exist_ok=True)
247            else:
248                path.parent.mkdir(parents=True, exist_ok=True)
249                path.write_bytes(content)
250                path.chmod(0o666)
251
252        for part_path, data in self.__parts.items():
253            if data is None:
254                # Deleted
255                continue
256            dump(part_path, data)
257
258    # Public API
259
260    def get_parts(self) -> list[str]:
261        """Get the list of members."""
262        if not self.path:
263            # maybe a file like zip archive
264            return list(self.__parts.keys())
265        if self.__packaging == "zip":
266            parts = []
267            with ZipFile(self.path) as zf:
268                for name in zf.namelist():
269                    upath = normalize_path(name)
270                    parts.append(upath)
271            return parts
272        elif self.__packaging == "folder":
273            return self._get_folder_parts()
274        else:
275            raise ValueError("Unable to provide parts of the document")
276
277    def get_part(self, path: str) -> str | bytes | None:
278        """Get the bytes of a part of the ODF."""
279        path = str(path)
280        if path in self.__parts:
281            part = self.__parts[path]
282            if part is None:
283                raise ValueError(f'Part "{path}" is deleted')
284            if self.__packaging == "folder":
285                cache_ts = self.__parts_ts.get(path, -1)
286                current_ts = self._get_folder_part_timestamp(path)
287                if current_ts != cache_ts:
288                    part, timestamp = self._get_folder_part(path)
289                    self.__parts[path] = part
290                    self.__parts_ts[path] = timestamp
291            return part
292        if self.__packaging == "zip":
293            return self._get_zip_part(path)
294        if self.__packaging == "folder":
295            part, timestamp = self._get_folder_part(path)
296            self.__parts[path] = part
297            self.__parts_ts[path] = timestamp
298            return part
299        return None
300
301    @property
302    def mimetype(self) -> str:
303        """Return str value of mimetype of the document."""
304        with contextlib.suppress(Exception):
305            b_mimetype = self.get_part("mimetype")
306            if isinstance(b_mimetype, bytes):
307                return bytes_to_str(b_mimetype)
308        return ""
309
310    @mimetype.setter
311    def mimetype(self, mimetype: str | bytes) -> None:
312        """Set mimetype value of the document."""
313        if isinstance(mimetype, str):
314            self.__parts["mimetype"] = str_to_bytes(mimetype)
315        elif isinstance(mimetype, bytes):
316            self.__parts["mimetype"] = mimetype
317        else:
318            raise TypeError(f'Wrong mimetype "{mimetype!r}"')
319
320    def set_part(self, path: str, data: bytes) -> None:
321        """Replace or add a new part."""
322        self.__parts[path] = data
323
324    def del_part(self, path: str) -> None:
325        """Mark a part for deletion."""
326        self.__parts[path] = None
327
328    @property
329    def clone(self) -> Container:
330        """Make a copy of this container with no path."""
331        if self.path and self.__packaging == "zip":
332            self._get_all_zip_part()
333        clone = deepcopy(self)
334        clone.path = None
335        return clone
336
337    @staticmethod
338    def _do_backup(target: str | Path) -> None:
339        path = Path(target)
340        if not path.exists():
341            return
342        back_file = Path(path.stem + ".backup" + path.suffix)
343        if back_file.is_dir():
344            try:
345                shutil.rmtree(back_file)
346            except OSError as e:
347                printwarn(str(e))
348        try:
349            shutil.move(target, back_file)
350        except OSError as e:
351            printwarn(str(e))
352
353    def _save_packaging(self, packaging: str | None) -> str:
354        if not packaging:
355            packaging = self.__packaging if self.__packaging else "zip"
356        packaging = packaging.strip().lower()
357        # if packaging not in ('zip', 'flat', 'folder'):
358        if packaging not in ("zip", "folder"):
359            raise ValueError(f'Packaging of type "{packaging}" is not supported')
360        return packaging
361
362    def _save_target(self, target: str | Path | io.BytesIO | None) -> str | io.BytesIO:
363        if target is None:
364            target = self.path
365        if isinstance(target, Path):
366            target = str(target)
367        if isinstance(target, str):
368            while target.endswith(os.sep):
369                target = target[:-1]
370            while target.endswith(".folder"):
371                target = target.split(".folder", 1)[0]
372        return target  # type: ignore
373
374    def _save_as_zip(self, target: str | Path | io.BytesIO, backup: bool) -> None:
375        if isinstance(target, (str, Path)) and backup:
376            self._do_backup(target)
377        self._save_zip(target)
378
379    def _save_as_folder(self, target: str | Path, backup: bool) -> None:
380        if not isinstance(target, (str, Path)):
381            raise TypeError(
382                f"Saving in folder format requires a folder name, not '{target!r}'"
383            )
384        if not str(target).endswith(".folder"):
385            target = str(target) + ".folder"
386        if backup:
387            self._do_backup(target)
388        else:
389            path = Path(target)
390            if path.exists():
391                try:
392                    shutil.rmtree(path)
393                except OSError as e:
394                    printwarn(str(e))
395        self._save_folder(target)
396
397    def save(
398        self,
399        target: str | Path | io.BytesIO | None,
400        packaging: str | None = None,
401        backup: bool = False,
402    ) -> None:
403        """Save the container to the given target, a path or a file-like
404        object.
405
406        Package the output document in the same format than current document,
407        unless "packaging" is different.
408
409        Arguments:
410
411            target -- str or file-like or Path
412
413            packaging -- 'zip', or for debugging purpose 'folder'
414
415            backup -- boolean
416        """
417        parts = self.__parts
418        packaging = self._save_packaging(packaging)
419        # Load parts else they will be considered deleted
420        for path in self.get_parts():
421            if path not in parts:
422                self.get_part(path)
423        target = self._save_target(target)
424        if packaging == "folder":
425            if isinstance(target, io.BytesIO):
426                raise TypeError(
427                    "Impossible to save on io.BytesIO with 'folder' packaging"
428                )
429            self._save_as_folder(target, backup)
430        else:
431            # default:
432            self._save_as_zip(target, backup)

Representation of the ODF file.

Container(path: pathlib.Path | str | _io.BytesIO | None = None)
58    def __init__(self, path: Path | str | io.BytesIO | None = None) -> None:
59        self.__parts: dict[str, bytes | None] = {}
60        self.__parts_ts: dict[str, int] = {}
61        self.__path_like: Path | str | io.BytesIO | None = None
62        self.__packaging: str = "zip"
63        self.path: Path | None = None  # or Path
64        if path:
65            self.open(path)
path: pathlib.Path | None
def open(self, path_or_file: pathlib.Path | str | _io.BytesIO) -> None:
70    def open(self, path_or_file: Path | str | io.BytesIO) -> None:
71        """Load the content of an ODF file."""
72        self.__path_like = path_or_file
73        if isinstance(path_or_file, (str, Path)):
74            self.path = Path(path_or_file).expanduser()
75            if not self.path.exists():
76                raise FileNotFoundError(str(self.path))
77            self.__path_like = self.path
78        if (self.path or isinstance(self.__path_like, io.BytesIO)) and is_zipfile(
79            self.__path_like  # type: ignore
80        ):
81            self.__packaging = "zip"
82            return self._read_zip()
83        if self.path:
84            is_folder = False
85            with contextlib.suppress(OSError):
86                is_folder = self.path.is_dir()
87            if is_folder:
88                self.__packaging = "folder"
89                return self._read_folder()
90        raise TypeError(f"Document format not managed by odfdo: {type(path_or_file)}.")

Load the content of an ODF file.

def get_parts(self) -> list[str]:
260    def get_parts(self) -> list[str]:
261        """Get the list of members."""
262        if not self.path:
263            # maybe a file like zip archive
264            return list(self.__parts.keys())
265        if self.__packaging == "zip":
266            parts = []
267            with ZipFile(self.path) as zf:
268                for name in zf.namelist():
269                    upath = normalize_path(name)
270                    parts.append(upath)
271            return parts
272        elif self.__packaging == "folder":
273            return self._get_folder_parts()
274        else:
275            raise ValueError("Unable to provide parts of the document")

Get the list of members.

def get_part(self, path: str) -> str | bytes | None:
277    def get_part(self, path: str) -> str | bytes | None:
278        """Get the bytes of a part of the ODF."""
279        path = str(path)
280        if path in self.__parts:
281            part = self.__parts[path]
282            if part is None:
283                raise ValueError(f'Part "{path}" is deleted')
284            if self.__packaging == "folder":
285                cache_ts = self.__parts_ts.get(path, -1)
286                current_ts = self._get_folder_part_timestamp(path)
287                if current_ts != cache_ts:
288                    part, timestamp = self._get_folder_part(path)
289                    self.__parts[path] = part
290                    self.__parts_ts[path] = timestamp
291            return part
292        if self.__packaging == "zip":
293            return self._get_zip_part(path)
294        if self.__packaging == "folder":
295            part, timestamp = self._get_folder_part(path)
296            self.__parts[path] = part
297            self.__parts_ts[path] = timestamp
298            return part
299        return None

Get the bytes of a part of the ODF.

mimetype: str
301    @property
302    def mimetype(self) -> str:
303        """Return str value of mimetype of the document."""
304        with contextlib.suppress(Exception):
305            b_mimetype = self.get_part("mimetype")
306            if isinstance(b_mimetype, bytes):
307                return bytes_to_str(b_mimetype)
308        return ""

Return str value of mimetype of the document.

def set_part(self, path: str, data: bytes) -> None:
320    def set_part(self, path: str, data: bytes) -> None:
321        """Replace or add a new part."""
322        self.__parts[path] = data

Replace or add a new part.

def del_part(self, path: str) -> None:
324    def del_part(self, path: str) -> None:
325        """Mark a part for deletion."""
326        self.__parts[path] = None

Mark a part for deletion.

clone: Container
328    @property
329    def clone(self) -> Container:
330        """Make a copy of this container with no path."""
331        if self.path and self.__packaging == "zip":
332            self._get_all_zip_part()
333        clone = deepcopy(self)
334        clone.path = None
335        return clone

Make a copy of this container with no path.

def save( self, target: str | pathlib.Path | _io.BytesIO | None, packaging: str | None = None, backup: bool = False) -> None:
397    def save(
398        self,
399        target: str | Path | io.BytesIO | None,
400        packaging: str | None = None,
401        backup: bool = False,
402    ) -> None:
403        """Save the container to the given target, a path or a file-like
404        object.
405
406        Package the output document in the same format than current document,
407        unless "packaging" is different.
408
409        Arguments:
410
411            target -- str or file-like or Path
412
413            packaging -- 'zip', or for debugging purpose 'folder'
414
415            backup -- boolean
416        """
417        parts = self.__parts
418        packaging = self._save_packaging(packaging)
419        # Load parts else they will be considered deleted
420        for path in self.get_parts():
421            if path not in parts:
422                self.get_part(path)
423        target = self._save_target(target)
424        if packaging == "folder":
425            if isinstance(target, io.BytesIO):
426                raise TypeError(
427                    "Impossible to save on io.BytesIO with 'folder' packaging"
428                )
429            self._save_as_folder(target, backup)
430        else:
431            # default:
432            self._save_as_zip(target, backup)

Save the container to the given target, a path or a file-like object.

Package the output document in the same format than current document, unless "packaging" is different.

Arguments:

target -- str or file-like or Path

packaging -- 'zip', or for debugging purpose 'folder'

backup -- boolean
class Content(odfdo.XmlPart):
 34class Content(XmlPart):
 35    @property
 36    def body(self) -> Element:
 37        body = self.root.document_body
 38        if not isinstance(body, Element):
 39            raise ValueError("No body found in document")  # noqa:TRY004
 40        return body
 41
 42    # The following two seem useless but they match styles API
 43
 44    def _get_style_contexts(self, family: str | None) -> tuple:
 45        if family == "font-face":
 46            return (self.get_element("//office:font-face-decls"),)
 47        return (
 48            self.get_element("//office:font-face-decls"),
 49            self.get_element("//office:automatic-styles"),
 50        )
 51
 52    def __str__(self) -> str:
 53        return str(self.body)
 54
 55    # Public API
 56
 57    def get_styles(self, family: str | None = None) -> list[Style]:
 58        """Return the list of styles in the Content part, optionally limited
 59        to the given family.
 60
 61        Arguments:
 62
 63            family -- str or None
 64
 65        Return: list of Style
 66        """
 67        result: list[Style] = []
 68        for context in self._get_style_contexts(family):
 69            if context is None:
 70                continue
 71            result.extend(context.get_styles(family=family))
 72        return result
 73
 74    def get_style(
 75        self,
 76        family: str,
 77        name_or_element: str | Element | None = None,
 78        display_name: str | None = None,
 79    ) -> Style | None:
 80        """Return the style uniquely identified by the name/family pair. If
 81        the argument is already a style object, it will return it.
 82
 83        If the name is None, the default style is fetched.
 84
 85        If the name is not the internal name but the name you gave in the
 86        desktop application, use display_name instead.
 87
 88        Arguments:
 89
 90            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
 91                      'number'
 92
 93            name_or_element -- str or Style
 94
 95            display_name -- str
 96
 97        Return: Style or None if not found
 98        """
 99        for context in self._get_style_contexts(family):
100            if context is None:
101                continue
102            style = context.get_style(
103                family,
104                name_or_element=name_or_element,
105                display_name=display_name,
106            )
107            if style is not None:
108                return style
109        return None

Representation of an XML part.

Abstraction of the XML library behind.

body: Element
35    @property
36    def body(self) -> Element:
37        body = self.root.document_body
38        if not isinstance(body, Element):
39            raise ValueError("No body found in document")  # noqa:TRY004
40        return body
def get_styles(self, family: str | None = None) -> list[Style]:
57    def get_styles(self, family: str | None = None) -> list[Style]:
58        """Return the list of styles in the Content part, optionally limited
59        to the given family.
60
61        Arguments:
62
63            family -- str or None
64
65        Return: list of Style
66        """
67        result: list[Style] = []
68        for context in self._get_style_contexts(family):
69            if context is None:
70                continue
71            result.extend(context.get_styles(family=family))
72        return result

Return the list of styles in the Content part, optionally limited to the given family.

Arguments:

family -- str or None

Return: list of Style

def get_style( self, family: str, name_or_element: str | Element | None = None, display_name: str | None = None) -> Style | None:
 74    def get_style(
 75        self,
 76        family: str,
 77        name_or_element: str | Element | None = None,
 78        display_name: str | None = None,
 79    ) -> Style | None:
 80        """Return the style uniquely identified by the name/family pair. If
 81        the argument is already a style object, it will return it.
 82
 83        If the name is None, the default style is fetched.
 84
 85        If the name is not the internal name but the name you gave in the
 86        desktop application, use display_name instead.
 87
 88        Arguments:
 89
 90            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
 91                      'number'
 92
 93            name_or_element -- str or Style
 94
 95            display_name -- str
 96
 97        Return: Style or None if not found
 98        """
 99        for context in self._get_style_contexts(family):
100            if context is None:
101                continue
102            style = context.get_style(
103                family,
104                name_or_element=name_or_element,
105                display_name=display_name,
106            )
107            if style is not None:
108                return style
109        return None

Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.

If the name is None, the default style is fetched.

If the name is not the internal name but the name you gave in the desktop application, use display_name instead.

Arguments:

family -- 'paragraph', 'text', 'graphic', 'table', 'list',
          'number'

name_or_element -- str or Style

display_name -- str

Return: Style or None if not found

class Document:
 159class Document:
 160    """Abstraction of the ODF document.
 161
 162    To create a new Document, several possibilities:
 163
 164        - Document() or Document("text") -> an "empty" document of type text
 165        - Document("spreadsheet") -> an "empty" document of type spreadsheet
 166        - Document("presentation") -> an "empty" document of type presentation
 167        - Document("drawing") -> an "empty" document of type drawing
 168
 169        Meaning of “empty”: these documents are copies of the default
 170        templates documents provided with this library, which, as templates,
 171        are not really empty. It may be useful to clear the newly created
 172        document: document.body.clear(), or adjust meta informations like
 173        description or default language: document.meta.set_language('fr-FR')
 174
 175    If the argument is not a known template type, or is a Path,
 176    Document(file) will load the content of the ODF file.
 177
 178    To explicitly create a document from a custom template, use the
 179    Document.new(path) method whose argument is the path to the template file.
 180    """
 181
 182    def __init__(
 183        self,
 184        target: str | bytes | Path | Container | io.BytesIO | None = "text",
 185    ) -> None:
 186        # Cache of XML parts
 187        self.__xmlparts: dict[str, XmlPart] = {}
 188        # Cache of the body
 189        self.__body: Element | None = None
 190        self.container: Container | None = None
 191        if isinstance(target, bytes):
 192            # eager conversion
 193            target = bytes_to_str(target)
 194        if target is None:
 195            # empty document, you probably don't wnat this.
 196            self.container = Container()
 197            return
 198        if isinstance(target, Path):
 199            # let's assume we open a container on existing file
 200            self.container = Container(target)
 201            return
 202        if isinstance(target, Container):
 203            # special internal case, use an existing container
 204            self.container = target
 205            return
 206        if isinstance(target, str):
 207            if target in ODF_TEMPLATES:
 208                # assuming a new document from templates
 209                self.container = container_from_template(target)
 210                return
 211            # let's assume we open a container on existing file
 212            self.container = Container(target)
 213            return
 214        if isinstance(target, io.BytesIO):
 215            self.container = Container(target)
 216            return
 217        raise TypeError(f"Unknown Document source type: '{target!r}'")
 218
 219    def __repr__(self) -> str:
 220        return f"<{self.__class__.__name__} type={self.get_type()} path={self.path}>"
 221
 222    def __str__(self) -> str:
 223        try:
 224            return str(self.get_formatted_text())
 225        except NotImplementedError:
 226            return self.body.text_recursive
 227
 228    @classmethod
 229    def new(cls, template: str | Path | io.BytesIO = "text") -> Document:
 230        """Create a Document from a template.
 231
 232        The template argument is expected to be the path to a ODF template.
 233
 234        Arguments:
 235
 236            template -- str or Path or file-like (io.BytesIO)
 237
 238        Return : ODF document -- Document
 239        """
 240        container = container_from_template(template)
 241        return cls(container)
 242
 243    # Public API
 244
 245    @property
 246    def path(self) -> Path | None:
 247        """Shortcut to Document.Container.path."""
 248        if not self.container:
 249            return None
 250        return self.container.path
 251
 252    @path.setter
 253    def path(self, path_or_str: str | Path) -> None:
 254        """Shortcut to Document.Container.path
 255
 256        Only accepting str or Path."""
 257        if not self.container:
 258            return
 259        self.container.path = Path(path_or_str)
 260
 261    def get_parts(self) -> list[str]:
 262        """Return available part names with path inside the archive, e.g.
 263        ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg']
 264        """
 265        if not self.container:
 266            raise ValueError("Empty Container")
 267        return self.container.get_parts()
 268
 269    def get_part(self, path: str) -> XmlPart | str | bytes | None:
 270        """Return the bytes of the given part. The path is relative to the
 271        archive, e.g. "Pictures/1003200258912EB1C3.jpg".
 272
 273        'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts
 274        to the real path, e.g. content.xml, and return a dedicated object with
 275        its own API.
 276
 277        path formated as URI, so always use '/' separator
 278        """
 279        if not self.container:
 280            raise ValueError("Empty Container")
 281        # "./ObjectReplacements/Object 1"
 282        path = path.lstrip("./")
 283        path = _get_part_path(path)
 284        cls = _get_part_class(path)
 285        # Raw bytes
 286        if cls is None:
 287            return self.container.get_part(path)
 288        # XML part
 289        part = self.__xmlparts.get(path)
 290        if part is None:
 291            self.__xmlparts[path] = part = cls(path, self.container)
 292        return part
 293
 294    def set_part(self, path: str, data: bytes) -> None:
 295        """Set the bytes of the given part. The path is relative to the
 296        archive, e.g. "Pictures/1003200258912EB1C3.jpg".
 297
 298        path formated as URI, so always use '/' separator
 299        """
 300        if not self.container:
 301            raise ValueError("Empty Container")
 302        # "./ObjectReplacements/Object 1"
 303        path = path.lstrip("./")
 304        path = _get_part_path(path)
 305        cls = _get_part_class(path)
 306        # XML part overwritten
 307        if cls is not None:
 308            with suppress(KeyError):
 309                self.__xmlparts[path]
 310        self.container.set_part(path, data)
 311
 312    def del_part(self, path: str) -> None:
 313        """Mark a part for deletion. The path is relative to the archive,
 314        e.g. "Pictures/1003200258912EB1C3.jpg"
 315        """
 316        if not self.container:
 317            raise ValueError("Empty Container")
 318        path = _get_part_path(path)
 319        cls = _get_part_class(path)
 320        if path == ODF_MANIFEST or cls is not None:
 321            raise ValueError(f"part '{path}' is mandatory")
 322        self.container.del_part(path)
 323
 324    @property
 325    def mimetype(self) -> str:
 326        if not self.container:
 327            raise ValueError("Empty Container")
 328        return self.container.mimetype
 329
 330    @mimetype.setter
 331    def mimetype(self, mimetype: str) -> None:
 332        if not self.container:
 333            raise ValueError("Empty Container")
 334        self.container.mimetype = mimetype
 335
 336    def get_type(self) -> str:
 337        """Get the ODF type (also called class) of this document.
 338
 339        Return: 'chart', 'database', 'formula', 'graphics',
 340            'graphics-template', 'image', 'presentation',
 341            'presentation-template', 'spreadsheet', 'spreadsheet-template',
 342            'text', 'text-master', 'text-template' or 'text-web'
 343        """
 344        # The mimetype must be with the form:
 345        # application/vnd.oasis.opendocument.text
 346
 347        # Isolate and return the last part
 348        return self.mimetype.rsplit(".", 1)[-1]
 349
 350    @property
 351    def body(self) -> Element:
 352        """Return the body element of the content part, where actual content
 353        is stored.
 354        """
 355        if self.__body is None:
 356            self.__body = self.content.body
 357        return self.__body
 358
 359    @property
 360    def meta(self) -> Meta:
 361        """Return the meta part (meta.xml) of the document, where meta data
 362        are stored."""
 363        metadata = self.get_part(ODF_META)
 364        if metadata is None or not isinstance(metadata, Meta):
 365            raise ValueError("Empty Meta")
 366        return metadata
 367
 368    @property
 369    def manifest(self) -> Manifest:
 370        """Return the manifest part (manifest.xml) of the document."""
 371        manifest = self.get_part(ODF_MANIFEST)
 372        if manifest is None or not isinstance(manifest, Manifest):
 373            raise ValueError("Empty Manifest")
 374        return manifest
 375
 376    def _get_formatted_text_footnotes(
 377        self,
 378        result: list[str],
 379        context: dict,
 380        rst_mode: bool,
 381    ) -> None:
 382        # Separate text from notes
 383        if rst_mode:
 384            result.append("\n")
 385        else:
 386            result.append("----\n")
 387        for citation, body in context["footnotes"]:
 388            if rst_mode:
 389                result.append(f".. [#] {body}\n")
 390            else:
 391                result.append(f"[{citation}] {body}\n")
 392        # Append a \n after the notes
 393        result.append("\n")
 394        # Reset for the next paragraph
 395        context["footnotes"] = []
 396
 397    def _get_formatted_text_annotations(
 398        self,
 399        result: list[str],
 400        context: dict,
 401        rst_mode: bool,
 402    ) -> None:
 403        # Insert the annotations
 404        # With a separation
 405        if rst_mode:
 406            result.append("\n")
 407        else:
 408            result.append("----\n")
 409        for annotation in context["annotations"]:
 410            if rst_mode:
 411                result.append(f".. [#] {annotation}\n")
 412            else:
 413                result.append(f"[*] {annotation}\n")
 414        context["annotations"] = []
 415
 416    def _get_formatted_text_images(
 417        self,
 418        result: list[str],
 419        context: dict,
 420        rst_mode: bool,
 421    ) -> None:
 422        # Insert the images ref, only in rst mode
 423        result.append("\n")
 424        for ref, filename, (width, height) in context["images"]:
 425            result.append(f".. {ref} image:: {filename}\n")
 426            if width is not None:
 427                result.append(f"   :width: {width}\n")
 428            if height is not None:
 429                result.append(f"   :height: {height}\n")
 430        context["images"] = []
 431
 432    def _get_formatted_text_endnotes(
 433        self,
 434        result: list[str],
 435        context: dict,
 436        rst_mode: bool,
 437    ) -> None:
 438        # Append the end notes
 439        if rst_mode:
 440            result.append("\n\n")
 441        else:
 442            result.append("\n========\n")
 443        for citation, body in context["endnotes"]:
 444            if rst_mode:
 445                result.append(f".. [*] {body}\n")
 446            else:
 447                result.append(f"({citation}) {body}\n")
 448
 449    def get_formatted_text(self, rst_mode: bool = False) -> str:
 450        """Return content as text, with some formatting."""
 451        # For the moment, only "type='text'"
 452        doc_type = self.get_type()
 453        if doc_type == "spreadsheet":
 454            return self._tables_csv()
 455        if doc_type in {
 456            "text",
 457            "text-template",
 458            "presentation",
 459            "presentation-template",
 460        }:
 461            return self._formatted_text(rst_mode)
 462        raise NotImplementedError(f"Type of document '{doc_type}' not supported yet")
 463
 464    def _tables_csv(self) -> str:
 465        return "\n\n".join(str(table) for table in self.body.get_tables())
 466
 467    def _formatted_text(self, rst_mode: bool) -> str:
 468        # Initialize an empty context
 469        context = {
 470            "document": self,
 471            "footnotes": [],
 472            "endnotes": [],
 473            "annotations": [],
 474            "rst_mode": rst_mode,
 475            "img_counter": 0,
 476            "images": [],
 477            "no_img_level": 0,
 478        }
 479        body = self.body
 480        # Get the text
 481        result = []
 482        for child in body.children:
 483            # self._get_formatted_text_child(result, element, context, rst_mode)
 484            # if child.tag == "table:table":
 485            #     result.append(child.get_formatted_text(context))
 486            #     return
 487            result.append(child.get_formatted_text(context))
 488            if context["footnotes"]:
 489                self._get_formatted_text_footnotes(result, context, rst_mode)
 490            if context["annotations"]:
 491                self._get_formatted_text_annotations(result, context, rst_mode)
 492            # Insert the images ref, only in rst mode
 493            if context["images"]:
 494                self._get_formatted_text_images(result, context, rst_mode)
 495        if context["endnotes"]:
 496            self._get_formatted_text_endnotes(result, context, rst_mode)
 497        return "".join(result)
 498
 499    def get_formated_meta(self) -> str:
 500        """Return meta informations as text, with some formatting."""
 501        result: list[str] = []
 502
 503        # Simple values
 504        def print_info(name: str, value: Any) -> None:
 505            if value:
 506                result.append(f"{name}: {value}")
 507
 508        meta = self.meta
 509        print_info("Title", meta.get_title())
 510        print_info("Subject", meta.get_subject())
 511        print_info("Language", meta.get_language())
 512        print_info("Modification date", meta.get_modification_date())
 513        print_info("Creation date", meta.get_creation_date())
 514        print_info("Initial creator", meta.get_initial_creator())
 515        print_info("Keyword", meta.get_keywords())
 516        print_info("Editing duration", meta.get_editing_duration())
 517        print_info("Editing cycles", meta.get_editing_cycles())
 518        print_info("Generator", meta.get_generator())
 519
 520        # Statistic
 521        result.append("Statistic:")
 522        statistic = meta.get_statistic()
 523        if statistic:
 524            for name, data in statistic.items():
 525                result.append(f"  - {name[5:].replace('-', ' ').capitalize()}: {data}")
 526
 527        # User defined metadata
 528        result.append("User defined metadata:")
 529        user_metadata = meta.get_user_defined_metadata()
 530        for name, data2 in user_metadata.items():
 531            result.append(f"  - {name}: {data2}")
 532
 533        # And the description
 534        print_info("Description", meta.get_description())
 535
 536        return "\n".join(result) + "\n"
 537
 538    def add_file(self, path_or_file: str | Path) -> str:
 539        """Insert a file from a path or a file-like object in the container.
 540
 541        Return the full path to reference in the content.
 542
 543        Arguments:
 544
 545            path_or_file -- str or Path or file-like
 546
 547        Return: str (URI)
 548        """
 549        if not self.container:
 550            raise ValueError("Empty Container")
 551        name = ""
 552        # Folder for added files (FIXME hard-coded and copied)
 553        manifest = self.manifest
 554        medias = manifest.get_paths()
 555        # uuid = str(uuid4())
 556
 557        if isinstance(path_or_file, (str, Path)):
 558            path = Path(path_or_file)
 559            extension = path.suffix.lower()
 560            name = f"{path.stem}{extension}"
 561            if posixpath.join("Pictures", name) in medias:
 562                name = f"{path.stem}_{uuid4()}{extension}"
 563        else:
 564            path = None
 565            name = getattr(path_or_file, "name", None)
 566            if not name:
 567                name = str(uuid4())
 568        media_type, _encoding = guess_type(name)
 569        if not media_type:
 570            media_type = "application/octet-stream"
 571        if manifest.get_media_type("Pictures/") is None:
 572            manifest.add_full_path("Pictures/")
 573        full_path = posixpath.join("Pictures", name)
 574        if path is None:
 575            self.container.set_part(full_path, path_or_file.read())  # type:ignore
 576        else:
 577            self.container.set_part(full_path, path.read_bytes())
 578        manifest.add_full_path(full_path, media_type)
 579        return full_path
 580
 581    @property
 582    def clone(self) -> Document:
 583        """Return an exact copy of the document.
 584
 585        Return: Document
 586        """
 587        clone = object.__new__(self.__class__)
 588        for name in self.__dict__:
 589            if name == "_Document__body":
 590                setattr(clone, name, None)
 591            elif name == "_Document__xmlparts":
 592                setattr(clone, name, {})
 593            elif name == "container":
 594                if not self.container:
 595                    raise ValueError("Empty Container")
 596                setattr(clone, name, self.container.clone)
 597            else:
 598                value = deepcopy(getattr(self, name))
 599                setattr(clone, name, value)
 600        return clone
 601
 602    def save(
 603        self,
 604        target: str | Path | io.BytesIO | None = None,
 605        packaging: str = ZIP,
 606        pretty: bool = False,
 607        backup: bool = False,
 608    ) -> None:
 609        """Save the document, at the same place it was opened or at the given
 610        target path. Target can also be a file-like object. It can be saved
 611        as a Zip file (default), flat XML format or as files in a folder
 612        (for debugging purpose). XML parts can be pretty printed.
 613
 614        Arguments:
 615
 616            target -- str or file-like object
 617
 618            packaging -- 'zip', 'folder', 'xml'
 619
 620            pretty -- bool
 621
 622            backup -- bool
 623        """
 624        if not self.container:
 625            raise ValueError("Empty Container")
 626        # Some advertising
 627        self.meta.set_generator_default()
 628        # Synchronize data with container
 629        container = self.container
 630        for path, part in self.__xmlparts.items():
 631            if part is not None:
 632                container.set_part(path, part.serialize(pretty))
 633        # Save the container
 634        container.save(target, packaging=packaging, backup=backup)
 635
 636    @property
 637    def content(self) -> Content:
 638        content: Content | None = self.get_part(ODF_CONTENT)  # type:ignore
 639        if content is None:
 640            raise ValueError("Empty Content")
 641        return content
 642
 643    @property
 644    def styles(self) -> Styles:
 645        styles: Styles | None = self.get_part(ODF_STYLES)  # type:ignore
 646        if styles is None:
 647            raise ValueError("Empty Styles")
 648        return styles
 649
 650    # Styles over several parts
 651
 652    def get_styles(
 653        self,
 654        family: str | bytes = "",
 655        automatic: bool = False,
 656    ) -> list[Style | Element]:
 657        # compatibility with old versions:
 658
 659        if isinstance(family, bytes):
 660            family = bytes_to_str(family)
 661        return self.content.get_styles(family=family) + self.styles.get_styles(
 662            family=family, automatic=automatic
 663        )
 664
 665    def get_style(
 666        self,
 667        family: str,
 668        name_or_element: str | Style | None = None,
 669        display_name: str | None = None,
 670    ) -> Style | None:
 671        """Return the style uniquely identified by the name/family pair. If
 672        the argument is already a style object, it will return it.
 673
 674        If the name is None, the default style is fetched.
 675
 676        If the name is not the internal name but the name you gave in a
 677        desktop application, use display_name instead.
 678
 679        Arguments:
 680
 681            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
 682                      'number', 'page-layout', 'master-page'
 683
 684            name -- str or Element or None
 685
 686            display_name -- str
 687
 688        Return: Style or None if not found.
 689        """
 690        # 1. content.xml
 691        element = self.content.get_style(
 692            family, name_or_element=name_or_element, display_name=display_name
 693        )
 694        if element is not None:
 695            return element
 696        # 2. styles.xml
 697        return self.styles.get_style(
 698            family,
 699            name_or_element=name_or_element,
 700            display_name=display_name,
 701        )
 702
 703    @staticmethod
 704    def _pseudo_style_attribute(style_element: Style | Element, attribute: str) -> Any:
 705        if hasattr(style_element, attribute):
 706            return getattr(style_element, attribute)
 707        return ""
 708
 709    def _set_automatic_name(self, style: Style, family: str) -> None:
 710        """Generate a name for the new automatic style."""
 711        if not hasattr(style, "name"):
 712            # do nothing
 713            return
 714        styles = self.get_styles(family=family, automatic=True)
 715        max_index = 0
 716        for existing_style in styles:
 717            if not hasattr(existing_style, "name"):
 718                continue
 719            if not existing_style.name.startswith(AUTOMATIC_PREFIX):
 720                continue
 721            try:
 722                index = int(existing_style.name[len(AUTOMATIC_PREFIX) :])  # type: ignore
 723            except ValueError:
 724                continue
 725            max_index = max(max_index, index)
 726
 727        style.name = f"{AUTOMATIC_PREFIX}{max_index+1}"
 728
 729    def _insert_style_get_common_styles(
 730        self,
 731        family: str,
 732        name: str,
 733    ) -> tuple[Any, Any]:
 734        style_container = self.styles.get_element("office:styles")
 735        existing = self.styles.get_style(family, name)
 736        return existing, style_container
 737
 738    def _insert_style_get_automatic_styles(
 739        self,
 740        style: Style,
 741        family: str,
 742        name: str,
 743    ) -> tuple[Any, Any]:
 744        style_container = self.content.get_element("office:automatic-styles")
 745        # A name ?
 746        if name:
 747            if hasattr(style, "name"):
 748                style.name = name
 749            existing = self.content.get_style(family, name)
 750        else:
 751            self._set_automatic_name(style, family)
 752            existing = None
 753        return existing, style_container
 754
 755    def _insert_style_get_default_styles(
 756        self,
 757        style: Style,
 758        family: str,
 759        name: str,
 760    ) -> tuple[Any, Any]:
 761        style_container = self.styles.get_element("office:styles")
 762        style.tag = "style:default-style"
 763        if name:
 764            style.del_attribute("style:name")
 765        existing = self.styles.get_style(family)
 766        return existing, style_container
 767
 768    def _insert_style_get_master_page(
 769        self,
 770        family: str,
 771        name: str,
 772    ) -> tuple[Any, Any]:
 773        style_container = self.styles.get_element("office:master-styles")
 774        existing = self.styles.get_style(family, name)
 775        return existing, style_container
 776
 777    def _insert_style_get_font_face_default(
 778        self,
 779        family: str,
 780        name: str,
 781    ) -> tuple[Any, Any]:
 782        style_container = self.styles.get_element("office:font-face-decls")
 783        existing = self.styles.get_style(family, name)
 784        return existing, style_container
 785
 786    def _insert_style_get_font_face(
 787        self,
 788        family: str,
 789        name: str,
 790    ) -> tuple[Any, Any]:
 791        style_container = self.content.get_element("office:font-face-decls")
 792        existing = self.content.get_style(family, name)
 793        return existing, style_container
 794
 795    def _insert_style_get_page_layout(
 796        self,
 797        family: str,
 798        name: str,
 799    ) -> tuple[Any, Any]:
 800        # force to automatic
 801        style_container = self.styles.get_element("office:automatic-styles")
 802        existing = self.styles.get_style(family, name)
 803        return existing, style_container
 804
 805    def _insert_style_get_draw_fill_image(
 806        self,
 807        name: str,
 808    ) -> tuple[Any, Any]:
 809        # special case for 'draw:fill-image' pseudo style
 810        # not family and style_element.__class__.__name__ == "DrawFillImage"
 811        style_container = self.styles.get_element("office:styles")
 812        existing = self.styles.get_style("", name)
 813        return existing, style_container
 814
 815    def _insert_style_standard(
 816        self,
 817        style: Style,
 818        name: str,
 819        family: str,
 820        automatic: bool,
 821        default: bool,
 822    ) -> tuple[Any, Any]:
 823        # Common style
 824        if name and automatic is False and default is False:
 825            return self._insert_style_get_common_styles(family, name)
 826        # Automatic style
 827        elif automatic is True and default is False:
 828            return self._insert_style_get_automatic_styles(style, family, name)
 829        # Default style
 830        elif automatic is False and default is True:
 831            return self._insert_style_get_default_styles(style, family, name)
 832        else:
 833            raise AttributeError("Invalid combination of arguments")
 834
 835    def insert_style(  # noqa: C901
 836        self,
 837        style: Style | str,
 838        name: str = "",
 839        automatic: bool = False,
 840        default: bool = False,
 841    ) -> Any:
 842        """Insert the given style object in the document, as required by the
 843        style family and type.
 844
 845        The style is expected to be a common style with a name. In case it
 846        was created with no name, the given can be set on the fly.
 847
 848        If automatic is True, the style will be inserted as an automatic
 849        style.
 850
 851        If default is True, the style will be inserted as a default style and
 852        would replace any existing default style of the same family. Any name
 853        or display name would be ignored.
 854
 855        Automatic and default arguments are mutually exclusive.
 856
 857        All styles can't be used as default styles. Default styles are
 858        allowed for the following families: paragraph, text, section, table,
 859        table-column, table-row, table-cell, table-page, chart, drawing-page,
 860        graphic, presentation, control and ruby.
 861
 862        Arguments:
 863
 864            style -- Style or str
 865
 866            name -- str
 867
 868            automatic -- bool
 869
 870            default -- bool
 871
 872        Return : style name -- str
 873        """
 874
 875        # if style is a str, assume it is the Style definition
 876        if isinstance(style, str):
 877            style_element: Style = Element.from_tag(style)  # type: ignore
 878        else:
 879            style_element = style
 880        if not isinstance(style_element, Element):
 881            raise TypeError(f"Unknown Style type: '{style!r}'")
 882
 883        # Get family and name
 884        family = self._pseudo_style_attribute(style_element, "family")
 885        if not name:
 886            name = self._pseudo_style_attribute(style_element, "name")
 887
 888        # Master page style
 889        if family == "master-page":
 890            existing, style_container = self._insert_style_get_master_page(family, name)
 891        # Font face declarations
 892        elif family == "font-face":
 893            if default:
 894                existing, style_container = self._insert_style_get_font_face_default(
 895                    family, name
 896                )
 897            else:
 898                existing, style_container = self._insert_style_get_font_face(
 899                    family, name
 900                )
 901        # page layout style
 902        elif family == "page-layout":
 903            existing, style_container = self._insert_style_get_page_layout(family, name)
 904        # Common style
 905        elif family in FAMILY_ODF_STD or family in {"number"}:
 906            existing, style_container = self._insert_style_standard(
 907                style_element, name, family, automatic, default
 908            )
 909        elif not family and style_element.__class__.__name__ == "DrawFillImage":
 910            # special case for 'draw:fill-image' pseudo style
 911            existing, style_container = self._insert_style_get_draw_fill_image(name)
 912        # Invalid style
 913        else:
 914            raise ValueError(
 915                "Invalid style: "
 916                f"{style_element}, tag:{style_element.tag}, family:{family}"
 917            )
 918
 919        # Insert it!
 920        if existing is not None:
 921            style_container.delete(existing)
 922        style_container.append(style_element)
 923        return self._pseudo_style_attribute(style_element, "name")
 924
 925    def get_styled_elements(self, name: str = "") -> list[Element]:
 926        """Brute-force to find paragraphs, tables, etc. using the given style
 927        name (or all by default).
 928
 929        Arguments:
 930
 931            name -- str
 932
 933        Return: list
 934        """
 935        # Header, footer, etc. have styles too
 936        return self.content.root.get_styled_elements(
 937            name
 938        ) + self.styles.root.get_styled_elements(name)
 939
 940    def show_styles(
 941        self,
 942        automatic: bool = True,
 943        common: bool = True,
 944        properties: bool = False,
 945    ) -> str:
 946        infos = []
 947        for style in self.get_styles():
 948            try:
 949                name = style.name  # type: ignore
 950            except AttributeError:
 951                print("--------------")
 952                print(style.__class__)
 953                print(style.serialize())
 954                raise
 955            if style.__class__.__name__ == "DrawFillImage":
 956                family = ""
 957            else:
 958                family = str(style.family)  # type: ignore
 959            parent = style.parent
 960            is_auto = parent and parent.tag == "office:automatic-styles"
 961            if is_auto and automatic is False or not is_auto and common is False:
 962                continue
 963            is_used = bool(self.get_styled_elements(name))
 964            infos.append(
 965                {
 966                    "type": "auto  " if is_auto else "common",
 967                    "used": "y" if is_used else "n",
 968                    "family": family,
 969                    "parent": self._pseudo_style_attribute(style, "parent_style") or "",
 970                    "name": name or "",
 971                    "display_name": self._pseudo_style_attribute(style, "display_name")
 972                    or "",
 973                    "properties": style.get_properties() if properties else None,  # type: ignore
 974                }
 975            )
 976        if not infos:
 977            return ""
 978        # Sort by family and name
 979        infos.sort(key=itemgetter("family", "name"))
 980        # Show common and used first
 981        infos.sort(key=itemgetter("type", "used"), reverse=True)
 982        max_family = str(max([len(x["family"]) for x in infos]))  # type: ignore
 983        max_parent = str(max([len(x["parent"]) for x in infos]))  # type: ignore
 984        formater = (
 985            "%(type)s used:%(used)s family:%(family)-0"
 986            + max_family
 987            + "s parent:%(parent)-0"
 988            + max_parent
 989            + "s name:%(name)s"
 990        )
 991        output = []
 992        for info in infos:
 993            line = formater % info
 994            if info["display_name"]:
 995                line += " display_name:" + info["display_name"]  # type: ignore
 996            output.append(line)
 997            if info["properties"]:
 998                for name, value in info["properties"].items():  # type: ignore
 999                    output.append(f"   - {name}: {value}")
1000        output.append("")
1001        return "\n".join(output)
1002
1003    def delete_styles(self) -> int:
1004        """Remove all style information from content and all styles.
1005
1006        Return: number of deleted styles
1007        """
1008        # First remove references to styles
1009        for element in self.get_styled_elements():
1010            for attribute in (
1011                "text:style-name",
1012                "draw:style-name",
1013                "draw:text-style-name",
1014                "table:style-name",
1015                "style:page-layout-name",
1016            ):
1017                try:
1018                    element.del_attribute(attribute)
1019                except KeyError:
1020                    continue
1021        # Then remove supposedly orphaned styles
1022        deleted = 0
1023        for style in self.get_styles():
1024            if style.name is None:  # type: ignore
1025                # Don't delete default styles
1026                continue
1027            # elif type(style) is odf_master_page:
1028            #    # Don't suppress header and footer, just styling was removed
1029            #    continue
1030            style.delete()
1031            deleted += 1
1032        return deleted
1033
1034    def merge_styles_from(self, document: Document) -> None:
1035        """Copy all the styles of a document into ourself.
1036
1037        Styles with the same type and name will be replaced, so only unique
1038        styles will be preserved.
1039        """
1040        manifest = self.manifest
1041        document_manifest = document.manifest
1042        for style in document.get_styles():
1043            tagname = style.tag
1044            family = self._pseudo_style_attribute(style, "family")
1045            stylename = style.name  # type: ignore
1046            container = style.parent
1047            container_name = container.tag  # type: ignore
1048            partname = container.parent.tag  # type: ignore
1049            # The destination part
1050            if partname == "office:document-styles":
1051                part: Content | Styles = self.styles
1052            elif partname == "office:document-content":
1053                part = self.content
1054            else:
1055                raise NotImplementedError(partname)
1056            # Implemented containers
1057            if container_name not in {
1058                "office:styles",
1059                "office:automatic-styles",
1060                "office:master-styles",
1061                "office:font-face-decls",
1062            }:
1063                raise NotImplementedError(container_name)
1064            dest = part.get_element(f"//{container_name}")
1065            # Implemented style types
1066            # if tagname not in registered_styles:
1067            #    raise NotImplementedError(tagname)
1068            duplicate = part.get_style(family, stylename)
1069            if duplicate is not None:
1070                duplicate.delete()
1071            dest.append(style)
1072            # Copy images from the header/footer
1073            if tagname == "style:master-page":
1074                query = "descendant::draw:image"
1075                for image in style.get_elements(query):
1076                    url = image.url  # type: ignore
1077                    part_url = document.get_part(url)
1078                    # Manually add the part to keep the name
1079                    self.set_part(url, part_url)  # type: ignore
1080                    media_type = document_manifest.get_media_type(url)
1081                    manifest.add_full_path(url, media_type)  # type: ignore
1082            # Copy images from the fill-image
1083            elif tagname == "draw:fill-image":
1084                url = style.url  # type: ignore
1085                part_url = document.get_part(url)
1086                self.set_part(url, part_url)  # type: ignore
1087                media_type = document_manifest.get_media_type(url)
1088                manifest.add_full_path(url, media_type)  # type: ignore
1089
1090    def add_page_break_style(self) -> None:
1091        """Ensure that the document contains the style required for a manual page break.
1092
1093        Then a manual page break can be added to the document with:
1094            from paragraph import PageBreak
1095            ...
1096            document.body.append(PageBreak())
1097
1098        Note: this style uses the property 'fo:break-after', another
1099        possibility could be the property 'fo:break-before'
1100        """
1101        if existing := self.get_style(  # noqa: SIM102
1102            family="paragraph",
1103            name_or_element="odfdopagebreak",
1104        ):
1105            if properties := existing.get_properties():  # noqa: SIM102
1106                if properties["fo:break-after"] == "page":
1107                    return
1108        style = (
1109            '<style:style style:family="paragraph" style:parent-style-name="Standard" '
1110            'style:name="odfdopagebreak">'
1111            '<style:paragraph-properties fo:break-after="page"/></style:style>'
1112        )
1113        self.insert_style(style, automatic=False)
1114
1115    def get_style_properties(
1116        self, family: str, name: str, area: str | None = None
1117    ) -> dict[str, str] | None:
1118        """Return the properties of the required style as a dict."""
1119        style = self.get_style(family, name)
1120        if style is None:
1121            return None
1122        return style.get_properties(area=area)  # type: ignore
1123
1124    def get_cell_style_properties(  # noqa: C901
1125        self, table: str | int, coord: tuple | list | str
1126    ) -> dict[str, str]:  # type: ignore
1127        """Return the style properties of a table cell of a .ods document,
1128        from the cell style or from the row style."""
1129
1130        def _get_table(table: int | str) -> Table | None:
1131            table_pos = 0
1132            table_name = None
1133            if isinstance(table, int):
1134                table_pos = table
1135            elif isinstance(table, str):
1136                table_name = table_name
1137            else:
1138                raise TypeError(f"Table parameter must be int or str: {table!r}")
1139            return self.body.get_table(
1140                position=table_pos, name=table_name  # type: ignore
1141            )
1142
1143        if not (sheet := _get_table(table)):
1144            return {}
1145        cell = sheet.get_cell(coord, clone=False)
1146        if cell.style:
1147            return (
1148                self.get_style_properties("table-cell", cell.style, "table-cell") or {}
1149            )
1150        try:
1151            row = sheet.get_row(cell.y, clone=False, create=False)  # type: ignore
1152            if row.style:  # noqa: SIM102
1153                if props := self.get_style_properties(
1154                    "table-row", row.style, "table-cell"
1155                ):
1156                    return props
1157            column = sheet.get_column(cell.x)  # type: ignore
1158            style = column.get_default_cell_style()
1159            if style:  # noqa: SIM102
1160                if props := self.get_style_properties(
1161                    "table-cell", style, "table-cell"
1162                ):
1163                    return props
1164        except ValueError:
1165            pass
1166        return {}
1167
1168    def get_cell_background_color(
1169        self,
1170        table: str | int,
1171        coord: tuple | list | str,
1172        default: str = "#ffffff",
1173    ) -> str:
1174        """Return the background color of a table cell of a .ods document,
1175        from the cell style, or from the row or column.
1176
1177        If color is not defined, return default value.."""
1178        found = self.get_cell_style_properties(table, coord).get("fo:background-color")
1179        return found or default

Abstraction of the ODF document.

To create a new Document, several possibilities:

- Document() or Document("text") -> an "empty" document of type text
- Document("spreadsheet") -> an "empty" document of type spreadsheet
- Document("presentation") -> an "empty" document of type presentation
- Document("drawing") -> an "empty" document of type drawing

Meaning of “empty”: these documents are copies of the default
templates documents provided with this library, which, as templates,
are not really empty. It may be useful to clear the newly created
document: document.body.clear(), or adjust meta informations like
description or default language: document.meta.set_language('fr-FR')

If the argument is not a known template type, or is a Path, Document(file) will load the content of the ODF file.

To explicitly create a document from a custom template, use the Document.new(path) method whose argument is the path to the template file.

Document( target: str | bytes | pathlib.Path | Container | _io.BytesIO | None = 'text')
182    def __init__(
183        self,
184        target: str | bytes | Path | Container | io.BytesIO | None = "text",
185    ) -> None:
186        # Cache of XML parts
187        self.__xmlparts: dict[str, XmlPart] = {}
188        # Cache of the body
189        self.__body: Element | None = None
190        self.container: Container | None = None
191        if isinstance(target, bytes):
192            # eager conversion
193            target = bytes_to_str(target)
194        if target is None:
195            # empty document, you probably don't wnat this.
196            self.container = Container()
197            return
198        if isinstance(target, Path):
199            # let's assume we open a container on existing file
200            self.container = Container(target)
201            return
202        if isinstance(target, Container):
203            # special internal case, use an existing container
204            self.container = target
205            return
206        if isinstance(target, str):
207            if target in ODF_TEMPLATES:
208                # assuming a new document from templates
209                self.container = container_from_template(target)
210                return
211            # let's assume we open a container on existing file
212            self.container = Container(target)
213            return
214        if isinstance(target, io.BytesIO):
215            self.container = Container(target)
216            return
217        raise TypeError(f"Unknown Document source type: '{target!r}'")
container: Container | None
@classmethod
def new( cls, template: str | pathlib.Path | _io.BytesIO = 'text') -> Document:
228    @classmethod
229    def new(cls, template: str | Path | io.BytesIO = "text") -> Document:
230        """Create a Document from a template.
231
232        The template argument is expected to be the path to a ODF template.
233
234        Arguments:
235
236            template -- str or Path or file-like (io.BytesIO)
237
238        Return : ODF document -- Document
239        """
240        container = container_from_template(template)
241        return cls(container)

Create a Document from a template.

The template argument is expected to be the path to a ODF template.

Arguments:

template -- str or Path or file-like (io.BytesIO)

Return : ODF document -- Document

path: pathlib.Path | None
245    @property
246    def path(self) -> Path | None:
247        """Shortcut to Document.Container.path."""
248        if not self.container:
249            return None
250        return self.container.path

Shortcut to Document.Container.path.

def get_parts(self) -> list[str]:
261    def get_parts(self) -> list[str]:
262        """Return available part names with path inside the archive, e.g.
263        ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg']
264        """
265        if not self.container:
266            raise ValueError("Empty Container")
267        return self.container.get_parts()

Return available part names with path inside the archive, e.g. ['content.xml', ..., 'Pictures/100000000000032000000258912EB1C3.jpg']

def get_part(self, path: str) -> XmlPart | str | bytes | None:
269    def get_part(self, path: str) -> XmlPart | str | bytes | None:
270        """Return the bytes of the given part. The path is relative to the
271        archive, e.g. "Pictures/1003200258912EB1C3.jpg".
272
273        'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts
274        to the real path, e.g. content.xml, and return a dedicated object with
275        its own API.
276
277        path formated as URI, so always use '/' separator
278        """
279        if not self.container:
280            raise ValueError("Empty Container")
281        # "./ObjectReplacements/Object 1"
282        path = path.lstrip("./")
283        path = _get_part_path(path)
284        cls = _get_part_class(path)
285        # Raw bytes
286        if cls is None:
287            return self.container.get_part(path)
288        # XML part
289        part = self.__xmlparts.get(path)
290        if part is None:
291            self.__xmlparts[path] = part = cls(path, self.container)
292        return part

Return the bytes of the given part. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg".

'content', 'meta', 'settings', 'styles' and 'manifest' are shortcuts to the real path, e.g. content.xml, and return a dedicated object with its own API.

path formated as URI, so always use '/' separator

def set_part(self, path: str, data: bytes) -> None:
294    def set_part(self, path: str, data: bytes) -> None:
295        """Set the bytes of the given part. The path is relative to the
296        archive, e.g. "Pictures/1003200258912EB1C3.jpg".
297
298        path formated as URI, so always use '/' separator
299        """
300        if not self.container:
301            raise ValueError("Empty Container")
302        # "./ObjectReplacements/Object 1"
303        path = path.lstrip("./")
304        path = _get_part_path(path)
305        cls = _get_part_class(path)
306        # XML part overwritten
307        if cls is not None:
308            with suppress(KeyError):
309                self.__xmlparts[path]
310        self.container.set_part(path, data)

Set the bytes of the given part. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg".

path formated as URI, so always use '/' separator

def del_part(self, path: str) -> None:
312    def del_part(self, path: str) -> None:
313        """Mark a part for deletion. The path is relative to the archive,
314        e.g. "Pictures/1003200258912EB1C3.jpg"
315        """
316        if not self.container:
317            raise ValueError("Empty Container")
318        path = _get_part_path(path)
319        cls = _get_part_class(path)
320        if path == ODF_MANIFEST or cls is not None:
321            raise ValueError(f"part '{path}' is mandatory")
322        self.container.del_part(path)

Mark a part for deletion. The path is relative to the archive, e.g. "Pictures/1003200258912EB1C3.jpg"

mimetype: str
324    @property
325    def mimetype(self) -> str:
326        if not self.container:
327            raise ValueError("Empty Container")
328        return self.container.mimetype
def get_type(self) -> str:
336    def get_type(self) -> str:
337        """Get the ODF type (also called class) of this document.
338
339        Return: 'chart', 'database', 'formula', 'graphics',
340            'graphics-template', 'image', 'presentation',
341            'presentation-template', 'spreadsheet', 'spreadsheet-template',
342            'text', 'text-master', 'text-template' or 'text-web'
343        """
344        # The mimetype must be with the form:
345        # application/vnd.oasis.opendocument.text
346
347        # Isolate and return the last part
348        return self.mimetype.rsplit(".", 1)[-1]

Get the ODF type (also called class) of this document.

Return: 'chart', 'database', 'formula', 'graphics', 'graphics-template', 'image', 'presentation', 'presentation-template', 'spreadsheet', 'spreadsheet-template', 'text', 'text-master', 'text-template' or 'text-web'

body: Element
350    @property
351    def body(self) -> Element:
352        """Return the body element of the content part, where actual content
353        is stored.
354        """
355        if self.__body is None:
356            self.__body = self.content.body
357        return self.__body

Return the body element of the content part, where actual content is stored.

meta: Meta
359    @property
360    def meta(self) -> Meta:
361        """Return the meta part (meta.xml) of the document, where meta data
362        are stored."""
363        metadata = self.get_part(ODF_META)
364        if metadata is None or not isinstance(metadata, Meta):
365            raise ValueError("Empty Meta")
366        return metadata

Return the meta part (meta.xml) of the document, where meta data are stored.

manifest: Manifest
368    @property
369    def manifest(self) -> Manifest:
370        """Return the manifest part (manifest.xml) of the document."""
371        manifest = self.get_part(ODF_MANIFEST)
372        if manifest is None or not isinstance(manifest, Manifest):
373            raise ValueError("Empty Manifest")
374        return manifest

Return the manifest part (manifest.xml) of the document.

def get_formatted_text(self, rst_mode: bool = False) -> str:
449    def get_formatted_text(self, rst_mode: bool = False) -> str:
450        """Return content as text, with some formatting."""
451        # For the moment, only "type='text'"
452        doc_type = self.get_type()
453        if doc_type == "spreadsheet":
454            return self._tables_csv()
455        if doc_type in {
456            "text",
457            "text-template",
458            "presentation",
459            "presentation-template",
460        }:
461            return self._formatted_text(rst_mode)
462        raise NotImplementedError(f"Type of document '{doc_type}' not supported yet")

Return content as text, with some formatting.

def get_formated_meta(self) -> str:
499    def get_formated_meta(self) -> str:
500        """Return meta informations as text, with some formatting."""
501        result: list[str] = []
502
503        # Simple values
504        def print_info(name: str, value: Any) -> None:
505            if value:
506                result.append(f"{name}: {value}")
507
508        meta = self.meta
509        print_info("Title", meta.get_title())
510        print_info("Subject", meta.get_subject())
511        print_info("Language", meta.get_language())
512        print_info("Modification date", meta.get_modification_date())
513        print_info("Creation date", meta.get_creation_date())
514        print_info("Initial creator", meta.get_initial_creator())
515        print_info("Keyword", meta.get_keywords())
516        print_info("Editing duration", meta.get_editing_duration())
517        print_info("Editing cycles", meta.get_editing_cycles())
518        print_info("Generator", meta.get_generator())
519
520        # Statistic
521        result.append("Statistic:")
522        statistic = meta.get_statistic()
523        if statistic:
524            for name, data in statistic.items():
525                result.append(f"  - {name[5:].replace('-', ' ').capitalize()}: {data}")
526
527        # User defined metadata
528        result.append("User defined metadata:")
529        user_metadata = meta.get_user_defined_metadata()
530        for name, data2 in user_metadata.items():
531            result.append(f"  - {name}: {data2}")
532
533        # And the description
534        print_info("Description", meta.get_description())
535
536        return "\n".join(result) + "\n"

Return meta informations as text, with some formatting.

def add_file(self, path_or_file: str | pathlib.Path) -> str:
538    def add_file(self, path_or_file: str | Path) -> str:
539        """Insert a file from a path or a file-like object in the container.
540
541        Return the full path to reference in the content.
542
543        Arguments:
544
545            path_or_file -- str or Path or file-like
546
547        Return: str (URI)
548        """
549        if not self.container:
550            raise ValueError("Empty Container")
551        name = ""
552        # Folder for added files (FIXME hard-coded and copied)
553        manifest = self.manifest
554        medias = manifest.get_paths()
555        # uuid = str(uuid4())
556
557        if isinstance(path_or_file, (str, Path)):
558            path = Path(path_or_file)
559            extension = path.suffix.lower()
560            name = f"{path.stem}{extension}"
561            if posixpath.join("Pictures", name) in medias:
562                name = f"{path.stem}_{uuid4()}{extension}"
563        else:
564            path = None
565            name = getattr(path_or_file, "name", None)
566            if not name:
567                name = str(uuid4())
568        media_type, _encoding = guess_type(name)
569        if not media_type:
570            media_type = "application/octet-stream"
571        if manifest.get_media_type("Pictures/") is None:
572            manifest.add_full_path("Pictures/")
573        full_path = posixpath.join("Pictures", name)
574        if path is None:
575            self.container.set_part(full_path, path_or_file.read())  # type:ignore
576        else:
577            self.container.set_part(full_path, path.read_bytes())
578        manifest.add_full_path(full_path, media_type)
579        return full_path

Insert a file from a path or a file-like object in the container.

Return the full path to reference in the content.

Arguments:

path_or_file -- str or Path or file-like

Return: str (URI)

clone: Document
581    @property
582    def clone(self) -> Document:
583        """Return an exact copy of the document.
584
585        Return: Document
586        """
587        clone = object.__new__(self.__class__)
588        for name in self.__dict__:
589            if name == "_Document__body":
590                setattr(clone, name, None)
591            elif name == "_Document__xmlparts":
592                setattr(clone, name, {})
593            elif name == "container":
594                if not self.container:
595                    raise ValueError("Empty Container")
596                setattr(clone, name, self.container.clone)
597            else:
598                value = deepcopy(getattr(self, name))
599                setattr(clone, name, value)
600        return clone

Return an exact copy of the document.

Return: Document

def save( self, target: str | pathlib.Path | _io.BytesIO | None = None, packaging: str = 'ZIP', pretty: bool = False, backup: bool = False) -> None:
602    def save(
603        self,
604        target: str | Path | io.BytesIO | None = None,
605        packaging: str = ZIP,
606        pretty: bool = False,
607        backup: bool = False,
608    ) -> None:
609        """Save the document, at the same place it was opened or at the given
610        target path. Target can also be a file-like object. It can be saved
611        as a Zip file (default), flat XML format or as files in a folder
612        (for debugging purpose). XML parts can be pretty printed.
613
614        Arguments:
615
616            target -- str or file-like object
617
618            packaging -- 'zip', 'folder', 'xml'
619
620            pretty -- bool
621
622            backup -- bool
623        """
624        if not self.container:
625            raise ValueError("Empty Container")
626        # Some advertising
627        self.meta.set_generator_default()
628        # Synchronize data with container
629        container = self.container
630        for path, part in self.__xmlparts.items():
631            if part is not None:
632                container.set_part(path, part.serialize(pretty))
633        # Save the container
634        container.save(target, packaging=packaging, backup=backup)

Save the document, at the same place it was opened or at the given target path. Target can also be a file-like object. It can be saved as a Zip file (default), flat XML format or as files in a folder (for debugging purpose). XML parts can be pretty printed.

Arguments:

target -- str or file-like object

packaging -- 'zip', 'folder', 'xml'

pretty -- bool

backup -- bool
content: Content
636    @property
637    def content(self) -> Content:
638        content: Content | None = self.get_part(ODF_CONTENT)  # type:ignore
639        if content is None:
640            raise ValueError("Empty Content")
641        return content
styles: Styles
643    @property
644    def styles(self) -> Styles:
645        styles: Styles | None = self.get_part(ODF_STYLES)  # type:ignore
646        if styles is None:
647            raise ValueError("Empty Styles")
648        return styles
def get_styles( self, family: str | bytes = '', automatic: bool = False) -> list[Style | Element]:
652    def get_styles(
653        self,
654        family: str | bytes = "",
655        automatic: bool = False,
656    ) -> list[Style | Element]:
657        # compatibility with old versions:
658
659        if isinstance(family, bytes):
660            family = bytes_to_str(family)
661        return self.content.get_styles(family=family) + self.styles.get_styles(
662            family=family, automatic=automatic
663        )
def get_style( self, family: str, name_or_element: str | Style | None = None, display_name: str | None = None) -> Style | None:
665    def get_style(
666        self,
667        family: str,
668        name_or_element: str | Style | None = None,
669        display_name: str | None = None,
670    ) -> Style | None:
671        """Return the style uniquely identified by the name/family pair. If
672        the argument is already a style object, it will return it.
673
674        If the name is None, the default style is fetched.
675
676        If the name is not the internal name but the name you gave in a
677        desktop application, use display_name instead.
678
679        Arguments:
680
681            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
682                      'number', 'page-layout', 'master-page'
683
684            name -- str or Element or None
685
686            display_name -- str
687
688        Return: Style or None if not found.
689        """
690        # 1. content.xml
691        element = self.content.get_style(
692            family, name_or_element=name_or_element, display_name=display_name
693        )
694        if element is not None:
695            return element
696        # 2. styles.xml
697        return self.styles.get_style(
698            family,
699            name_or_element=name_or_element,
700            display_name=display_name,
701        )

Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.

If the name is None, the default style is fetched.

If the name is not the internal name but the name you gave in a desktop application, use display_name instead.

Arguments:

family -- 'paragraph', 'text',  'graphic', 'table', 'list',
          'number', 'page-layout', 'master-page'

name -- str or Element or None

display_name -- str

Return: Style or None if not found.

def insert_style( self, style: Style | str, name: str = '', automatic: bool = False, default: bool = False) -> Any:
835    def insert_style(  # noqa: C901
836        self,
837        style: Style | str,
838        name: str = "",
839        automatic: bool = False,
840        default: bool = False,
841    ) -> Any:
842        """Insert the given style object in the document, as required by the
843        style family and type.
844
845        The style is expected to be a common style with a name. In case it
846        was created with no name, the given can be set on the fly.
847
848        If automatic is True, the style will be inserted as an automatic
849        style.
850
851        If default is True, the style will be inserted as a default style and
852        would replace any existing default style of the same family. Any name
853        or display name would be ignored.
854
855        Automatic and default arguments are mutually exclusive.
856
857        All styles can't be used as default styles. Default styles are
858        allowed for the following families: paragraph, text, section, table,
859        table-column, table-row, table-cell, table-page, chart, drawing-page,
860        graphic, presentation, control and ruby.
861
862        Arguments:
863
864            style -- Style or str
865
866            name -- str
867
868            automatic -- bool
869
870            default -- bool
871
872        Return : style name -- str
873        """
874
875        # if style is a str, assume it is the Style definition
876        if isinstance(style, str):
877            style_element: Style = Element.from_tag(style)  # type: ignore
878        else:
879            style_element = style
880        if not isinstance(style_element, Element):
881            raise TypeError(f"Unknown Style type: '{style!r}'")
882
883        # Get family and name
884        family = self._pseudo_style_attribute(style_element, "family")
885        if not name:
886            name = self._pseudo_style_attribute(style_element, "name")
887
888        # Master page style
889        if family == "master-page":
890            existing, style_container = self._insert_style_get_master_page(family, name)
891        # Font face declarations
892        elif family == "font-face":
893            if default:
894                existing, style_container = self._insert_style_get_font_face_default(
895                    family, name
896                )
897            else:
898                existing, style_container = self._insert_style_get_font_face(
899                    family, name
900                )
901        # page layout style
902        elif family == "page-layout":
903            existing, style_container = self._insert_style_get_page_layout(family, name)
904        # Common style
905        elif family in FAMILY_ODF_STD or family in {"number"}:
906            existing, style_container = self._insert_style_standard(
907                style_element, name, family, automatic, default
908            )
909        elif not family and style_element.__class__.__name__ == "DrawFillImage":
910            # special case for 'draw:fill-image' pseudo style
911            existing, style_container = self._insert_style_get_draw_fill_image(name)
912        # Invalid style
913        else:
914            raise ValueError(
915                "Invalid style: "
916                f"{style_element}, tag:{style_element.tag}, family:{family}"
917            )
918
919        # Insert it!
920        if existing is not None:
921            style_container.delete(existing)
922        style_container.append(style_element)
923        return self._pseudo_style_attribute(style_element, "name")

Insert the given style object in the document, as required by the style family and type.

The style is expected to be a common style with a name. In case it was created with no name, the given can be set on the fly.

If automatic is True, the style will be inserted as an automatic style.

If default is True, the style will be inserted as a default style and would replace any existing default style of the same family. Any name or display name would be ignored.

Automatic and default arguments are mutually exclusive.

All styles can't be used as default styles. Default styles are allowed for the following families: paragraph, text, section, table, table-column, table-row, table-cell, table-page, chart, drawing-page, graphic, presentation, control and ruby.

Arguments:

style -- Style or str

name -- str

automatic -- bool

default -- bool

Return : style name -- str

def get_styled_elements(self, name: str = '') -> list[Element]:
925    def get_styled_elements(self, name: str = "") -> list[Element]:
926        """Brute-force to find paragraphs, tables, etc. using the given style
927        name (or all by default).
928
929        Arguments:
930
931            name -- str
932
933        Return: list
934        """
935        # Header, footer, etc. have styles too
936        return self.content.root.get_styled_elements(
937            name
938        ) + self.styles.root.get_styled_elements(name)

Brute-force to find paragraphs, tables, etc. using the given style name (or all by default).

Arguments:

name -- str

Return: list

def show_styles( self, automatic: bool = True, common: bool = True, properties: bool = False) -> str:
 940    def show_styles(
 941        self,
 942        automatic: bool = True,
 943        common: bool = True,
 944        properties: bool = False,
 945    ) -> str:
 946        infos = []
 947        for style in self.get_styles():
 948            try:
 949                name = style.name  # type: ignore
 950            except AttributeError:
 951                print("--------------")
 952                print(style.__class__)
 953                print(style.serialize())
 954                raise
 955            if style.__class__.__name__ == "DrawFillImage":
 956                family = ""
 957            else:
 958                family = str(style.family)  # type: ignore
 959            parent = style.parent
 960            is_auto = parent and parent.tag == "office:automatic-styles"
 961            if is_auto and automatic is False or not is_auto and common is False:
 962                continue
 963            is_used = bool(self.get_styled_elements(name))
 964            infos.append(
 965                {
 966                    "type": "auto  " if is_auto else "common",
 967                    "used": "y" if is_used else "n",
 968                    "family": family,
 969                    "parent": self._pseudo_style_attribute(style, "parent_style") or "",
 970                    "name": name or "",
 971                    "display_name": self._pseudo_style_attribute(style, "display_name")
 972                    or "",
 973                    "properties": style.get_properties() if properties else None,  # type: ignore
 974                }
 975            )
 976        if not infos:
 977            return ""
 978        # Sort by family and name
 979        infos.sort(key=itemgetter("family", "name"))
 980        # Show common and used first
 981        infos.sort(key=itemgetter("type", "used"), reverse=True)
 982        max_family = str(max([len(x["family"]) for x in infos]))  # type: ignore
 983        max_parent = str(max([len(x["parent"]) for x in infos]))  # type: ignore
 984        formater = (
 985            "%(type)s used:%(used)s family:%(family)-0"
 986            + max_family
 987            + "s parent:%(parent)-0"
 988            + max_parent
 989            + "s name:%(name)s"
 990        )
 991        output = []
 992        for info in infos:
 993            line = formater % info
 994            if info["display_name"]:
 995                line += " display_name:" + info["display_name"]  # type: ignore
 996            output.append(line)
 997            if info["properties"]:
 998                for name, value in info["properties"].items():  # type: ignore
 999                    output.append(f"   - {name}: {value}")
1000        output.append("")
1001        return "\n".join(output)
def delete_styles(self) -> int:
1003    def delete_styles(self) -> int:
1004        """Remove all style information from content and all styles.
1005
1006        Return: number of deleted styles
1007        """
1008        # First remove references to styles
1009        for element in self.get_styled_elements():
1010            for attribute in (
1011                "text:style-name",
1012                "draw:style-name",
1013                "draw:text-style-name",
1014                "table:style-name",
1015                "style:page-layout-name",
1016            ):
1017                try:
1018                    element.del_attribute(attribute)
1019                except KeyError:
1020                    continue
1021        # Then remove supposedly orphaned styles
1022        deleted = 0
1023        for style in self.get_styles():
1024            if style.name is None:  # type: ignore
1025                # Don't delete default styles
1026                continue
1027            # elif type(style) is odf_master_page:
1028            #    # Don't suppress header and footer, just styling was removed
1029            #    continue
1030            style.delete()
1031            deleted += 1
1032        return deleted

Remove all style information from content and all styles.

Return: number of deleted styles

def merge_styles_from(self, document: Document) -> None:
1034    def merge_styles_from(self, document: Document) -> None:
1035        """Copy all the styles of a document into ourself.
1036
1037        Styles with the same type and name will be replaced, so only unique
1038        styles will be preserved.
1039        """
1040        manifest = self.manifest
1041        document_manifest = document.manifest
1042        for style in document.get_styles():
1043            tagname = style.tag
1044            family = self._pseudo_style_attribute(style, "family")
1045            stylename = style.name  # type: ignore
1046            container = style.parent
1047            container_name = container.tag  # type: ignore
1048            partname = container.parent.tag  # type: ignore
1049            # The destination part
1050            if partname == "office:document-styles":
1051                part: Content | Styles = self.styles
1052            elif partname == "office:document-content":
1053                part = self.content
1054            else:
1055                raise NotImplementedError(partname)
1056            # Implemented containers
1057            if container_name not in {
1058                "office:styles",
1059                "office:automatic-styles",
1060                "office:master-styles",
1061                "office:font-face-decls",
1062            }:
1063                raise NotImplementedError(container_name)
1064            dest = part.get_element(f"//{container_name}")
1065            # Implemented style types
1066            # if tagname not in registered_styles:
1067            #    raise NotImplementedError(tagname)
1068            duplicate = part.get_style(family, stylename)
1069            if duplicate is not None:
1070                duplicate.delete()
1071            dest.append(style)
1072            # Copy images from the header/footer
1073            if tagname == "style:master-page":
1074                query = "descendant::draw:image"
1075                for image in style.get_elements(query):
1076                    url = image.url  # type: ignore
1077                    part_url = document.get_part(url)
1078                    # Manually add the part to keep the name
1079                    self.set_part(url, part_url)  # type: ignore
1080                    media_type = document_manifest.get_media_type(url)
1081                    manifest.add_full_path(url, media_type)  # type: ignore
1082            # Copy images from the fill-image
1083            elif tagname == "draw:fill-image":
1084                url = style.url  # type: ignore
1085                part_url = document.get_part(url)
1086                self.set_part(url, part_url)  # type: ignore
1087                media_type = document_manifest.get_media_type(url)
1088                manifest.add_full_path(url, media_type)  # type: ignore

Copy all the styles of a document into ourself.

Styles with the same type and name will be replaced, so only unique styles will be preserved.

def add_page_break_style(self) -> None:
1090    def add_page_break_style(self) -> None:
1091        """Ensure that the document contains the style required for a manual page break.
1092
1093        Then a manual page break can be added to the document with:
1094            from paragraph import PageBreak
1095            ...
1096            document.body.append(PageBreak())
1097
1098        Note: this style uses the property 'fo:break-after', another
1099        possibility could be the property 'fo:break-before'
1100        """
1101        if existing := self.get_style(  # noqa: SIM102
1102            family="paragraph",
1103            name_or_element="odfdopagebreak",
1104        ):
1105            if properties := existing.get_properties():  # noqa: SIM102
1106                if properties["fo:break-after"] == "page":
1107                    return
1108        style = (
1109            '<style:style style:family="paragraph" style:parent-style-name="Standard" '
1110            'style:name="odfdopagebreak">'
1111            '<style:paragraph-properties fo:break-after="page"/></style:style>'
1112        )
1113        self.insert_style(style, automatic=False)

Ensure that the document contains the style required for a manual page break.

Then a manual page break can be added to the document with: from paragraph import PageBreak ... document.body.append(PageBreak())

Note: this style uses the property 'fo:break-after', another possibility could be the property 'fo:break-before'

def get_style_properties( self, family: str, name: str, area: str | None = None) -> dict[str, str] | None:
1115    def get_style_properties(
1116        self, family: str, name: str, area: str | None = None
1117    ) -> dict[str, str] | None:
1118        """Return the properties of the required style as a dict."""
1119        style = self.get_style(family, name)
1120        if style is None:
1121            return None
1122        return style.get_properties(area=area)  # type: ignore

Return the properties of the required style as a dict.

def get_cell_style_properties(self, table: str | int, coord: tuple | list | str) -> dict[str, str]:
1124    def get_cell_style_properties(  # noqa: C901
1125        self, table: str | int, coord: tuple | list | str
1126    ) -> dict[str, str]:  # type: ignore
1127        """Return the style properties of a table cell of a .ods document,
1128        from the cell style or from the row style."""
1129
1130        def _get_table(table: int | str) -> Table | None:
1131            table_pos = 0
1132            table_name = None
1133            if isinstance(table, int):
1134                table_pos = table
1135            elif isinstance(table, str):
1136                table_name = table_name
1137            else:
1138                raise TypeError(f"Table parameter must be int or str: {table!r}")
1139            return self.body.get_table(
1140                position=table_pos, name=table_name  # type: ignore
1141            )
1142
1143        if not (sheet := _get_table(table)):
1144            return {}
1145        cell = sheet.get_cell(coord, clone=False)
1146        if cell.style:
1147            return (
1148                self.get_style_properties("table-cell", cell.style, "table-cell") or {}
1149            )
1150        try:
1151            row = sheet.get_row(cell.y, clone=False, create=False)  # type: ignore
1152            if row.style:  # noqa: SIM102
1153                if props := self.get_style_properties(
1154                    "table-row", row.style, "table-cell"
1155                ):
1156                    return props
1157            column = sheet.get_column(cell.x)  # type: ignore
1158            style = column.get_default_cell_style()
1159            if style:  # noqa: SIM102
1160                if props := self.get_style_properties(
1161                    "table-cell", style, "table-cell"
1162                ):
1163                    return props
1164        except ValueError:
1165            pass
1166        return {}

Return the style properties of a table cell of a .ods document, from the cell style or from the row style.

def get_cell_background_color( self, table: str | int, coord: tuple | list | str, default: str = '#ffffff') -> str:
1168    def get_cell_background_color(
1169        self,
1170        table: str | int,
1171        coord: tuple | list | str,
1172        default: str = "#ffffff",
1173    ) -> str:
1174        """Return the background color of a table cell of a .ods document,
1175        from the cell style, or from the row or column.
1176
1177        If color is not defined, return default value.."""
1178        found = self.get_cell_style_properties(table, coord).get("fo:background-color")
1179        return found or default

Return the background color of a table cell of a .ods document, from the cell style, or from the row or column.

If color is not defined, return default value..

class DrawFillImage(odfdo.DrawImage):
 85class DrawFillImage(DrawImage):
 86    _tag = "draw:fill-image"
 87    _properties: tuple[PropDef, ...] = (
 88        PropDef("display_name", "draw:display-name"),
 89        PropDef("name", "draw:name"),
 90        PropDef("height", "svg:height"),
 91        PropDef("width", "svg:width"),
 92    )
 93
 94    def __init__(
 95        self,
 96        name: str | None = None,
 97        display_name: str | None = None,
 98        height: str | None = None,
 99        width: str | None = None,
100        **kwargs: Any,
101    ) -> None:
102        """The "draw:fill-image" element specifies a link to a bitmap
103        resource. Fill image are not available as automatic styles.
104        The "draw:fill-image" element is usable within the following element:
105        "office:styles"
106
107        Arguments:
108
109            name -- str
110
111            display_name -- str
112
113            height -- str
114
115            width -- str
116        """
117        super().__init__(**kwargs)
118        if self._do_init:
119            self.name = name
120            self.display_name = display_name
121            self.height = height
122            self.width = width

The "draw:image" element represents an image. An image can be either:

  • A link to an external resource or
  • Embedded in the document (Not implemented in this version)

Warning: image elements must be stored in a frame "draw:frame", see Frame().

DrawFillImage( name: str | None = None, display_name: str | None = None, height: str | None = None, width: str | None = None, **kwargs: Any)
 94    def __init__(
 95        self,
 96        name: str | None = None,
 97        display_name: str | None = None,
 98        height: str | None = None,
 99        width: str | None = None,
100        **kwargs: Any,
101    ) -> None:
102        """The "draw:fill-image" element specifies a link to a bitmap
103        resource. Fill image are not available as automatic styles.
104        The "draw:fill-image" element is usable within the following element:
105        "office:styles"
106
107        Arguments:
108
109            name -- str
110
111            display_name -- str
112
113            height -- str
114
115            width -- str
116        """
117        super().__init__(**kwargs)
118        if self._do_init:
119            self.name = name
120            self.display_name = display_name
121            self.height = height
122            self.width = width

The "draw:fill-image" element specifies a link to a bitmap resource. Fill image are not available as automatic styles. The "draw:fill-image" element is usable within the following element: "office:styles"

Arguments:

name -- str

display_name -- str

height -- str

width -- str
display_name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
height: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
width: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
DrawImage
url
type
show
actuate
filter_name
class DrawGroup(odfdo.Element, odfdo.frame.AnchorMix, odfdo.frame.ZMix, odfdo.frame.PosMix):
315class DrawGroup(Element, AnchorMix, ZMix, PosMix):
316    """The DrawGroup "draw:g" element represents a group of drawing shapes.
317
318    Warning: implementation is currently minimal.
319
320    Drawing shapes contained by a "draw:g" element that is itself
321    contained by a "draw:a" element, act as hyperlinks using the
322    xlink:href attribute of the containing "draw:a" element. If the
323    included drawing shapes are themselves contained within "draw:a"
324    elements, then the xlink:href attributes of those "draw:a" elements
325    act as the hyperlink information for the shapes they contain.
326
327    The "draw:g" element has the following attributes: draw:caption-id,
328    draw:class-names, draw:id, draw:name, draw:style-name, draw:z-index,
329    presentation:class-names, presentation:style-name, svg:y,
330    table:end-cell-address, table:end-x, table:end-y,
331    table:table-background, text:anchor-page-number, text:anchor-type,
332    and xml:id.
333
334    The "draw:g" element has the following child elements: "dr3d:scene",
335    "draw:a", "draw:caption", "draw:circle", "draw:connector",
336    "draw:control", "draw:custom-shape", "draw:ellipse", "draw:frame",
337    "draw:g", "draw:glue-point", "draw:line", "draw:measure",
338    "draw:page-thumbnail", "draw:path", "draw:polygon", "draw:polyline",
339    "draw:rect", "draw:regular-polygon", "office:event-listeners",
340    "svg:desc" and "svg:title".
341    """
342
343    _tag = "draw:g"
344    _properties: tuple[PropDef, ...] = (
345        PropDef("draw_id", "draw:id"),
346        PropDef("caption_id", "draw:caption-id"),
347        PropDef("draw_class_names", "draw:class-names"),
348        PropDef("name", "draw:name"),
349        PropDef("style", "draw:style-name"),
350        # ('z_index', 'draw:z-index'),
351        PropDef("presentation_class_names", "presentation:class-names"),
352        PropDef("presentation_style", "presentation:style-name"),
353        PropDef("table_end_cell", "table:end-cell-address"),
354        PropDef("table_end_x", "table:end-x"),
355        PropDef("table_end_y", "table:end-y"),
356        PropDef("table_background", "table:table-background"),
357        # ('anchor_page', 'text:anchor-page-number'),
358        # ('anchor_type', 'text:anchor-type'),
359        PropDef("xml_id", "xml:id"),
360        PropDef("pos_x", "svg:x"),
361        PropDef("pos_y", "svg:y"),
362    )
363
364    def __init__(
365        self,
366        name: str | None = None,
367        draw_id: str | None = None,
368        style: str | None = None,
369        position: tuple | None = None,
370        z_index: int = 0,
371        anchor_type: str | None = None,
372        anchor_page: int | None = None,
373        presentation_style: str | None = None,
374        **kwargs: Any,
375    ) -> None:
376        super().__init__(**kwargs)
377        if self._do_init:
378            if z_index is not None:
379                self.z_index = z_index
380            if name:
381                self.name = name
382            if draw_id is not None:
383                self.draw_id = draw_id
384            if style is not None:
385                self.style = style
386            if position is not None:
387                self.position = position
388            if anchor_type:
389                self.anchor_type = anchor_type
390            if anchor_page is not None:
391                self.anchor_page = anchor_page
392            if presentation_style is not None:
393                self.presentation_style = presentation_style

The DrawGroup "draw:g" element represents a group of drawing shapes.

Warning: implementation is currently minimal.

Drawing shapes contained by a "draw:g" element that is itself contained by a "draw:a" element, act as hyperlinks using the xlink:href attribute of the containing "draw:a" element. If the included drawing shapes are themselves contained within "draw:a" elements, then the xlink:href attributes of those "draw:a" elements act as the hyperlink information for the shapes they contain.

The "draw:g" element has the following attributes: draw:caption-id, draw:class-names, draw:id, draw:name, draw:style-name, draw:z-index, presentation:class-names, presentation:style-name, svg:y, table:end-cell-address, table:end-x, table:end-y, table:table-background, text:anchor-page-number, text:anchor-type, and xml:id.

The "draw:g" element has the following child elements: "dr3d:scene", "draw:a", "draw:caption", "draw:circle", "draw:connector", "draw:control", "draw:custom-shape", "draw:ellipse", "draw:frame", "draw:g", "draw:glue-point", "draw:line", "draw:measure", "draw:page-thumbnail", "draw:path", "draw:polygon", "draw:polyline", "draw:rect", "draw:regular-polygon", "office:event-listeners", "svg:desc" and "svg:title".

DrawGroup( name: str | None = None, draw_id: str | None = None, style: str | None = None, position: tuple | None = None, z_index: int = 0, anchor_type: str | None = None, anchor_page: int | None = None, presentation_style: str | None = None, **kwargs: Any)
364    def __init__(
365        self,
366        name: str | None = None,
367        draw_id: str | None = None,
368        style: str | None = None,
369        position: tuple | None = None,
370        z_index: int = 0,
371        anchor_type: str | None = None,
372        anchor_page: int | None = None,
373        presentation_style: str | None = None,
374        **kwargs: Any,
375    ) -> None:
376        super().__init__(**kwargs)
377        if self._do_init:
378            if z_index is not None:
379                self.z_index = z_index
380            if name:
381                self.name = name
382            if draw_id is not None:
383                self.draw_id = draw_id
384            if style is not None:
385                self.style = style
386            if position is not None:
387                self.position = position
388            if anchor_type:
389                self.anchor_type = anchor_type
390            if anchor_page is not None:
391                self.anchor_page = anchor_page
392            if presentation_style is not None:
393                self.presentation_style = presentation_style
draw_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
caption_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
draw_class_names: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
presentation_class_names: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
presentation_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
table_end_cell: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
table_end_x: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
table_end_y: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
table_background: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
xml_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
pos_x: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
pos_y: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.AnchorMix
ANCHOR_VALUE_CHOICE
anchor_type
anchor_page
odfdo.frame.ZMix
z_index
odfdo.frame.PosMix
position
class DrawImage(odfdo.Element):
31class DrawImage(Element):
32    """The "draw:image" element represents an image. An image can be
33    either:
34    - A link to an external resource or
35    - Embedded in the document (Not implemented in this version)
36
37    Warning: image elements must be stored in a frame "draw:frame",
38    see Frame().
39    """
40
41    _tag = "draw:image"
42    _properties: tuple[PropDef, ...] = (
43        PropDef("url", "xlink:href"),
44        PropDef("type", "xlink:type"),
45        PropDef("show", "xlink:show"),
46        PropDef("actuate", "xlink:actuate"),
47        PropDef("filter_name", "draw:filter-name"),
48    )
49
50    def __init__(
51        self,
52        url: str = "",
53        xlink_type: str = "simple",
54        show: str = "embed",
55        actuate: str = "onLoad",
56        filter_name: str | None = None,
57        **kwargs: Any,
58    ) -> None:
59        """Initialisation of an DrawImage.
60
61        Arguments:
62
63            url -- str
64
65            type -- str
66
67            show -- str
68
69            actuate -- str
70
71            filter_name -- str
72        """
73        super().__init__(**kwargs)
74        if self._do_init:
75            self.url = url
76            self.type = xlink_type
77            self.show = show
78            self.actuate = actuate
79            self.filter_name = filter_name

The "draw:image" element represents an image. An image can be either:

  • A link to an external resource or
  • Embedded in the document (Not implemented in this version)

Warning: image elements must be stored in a frame "draw:frame", see Frame().

DrawImage( url: str = '', xlink_type: str = 'simple', show: str = 'embed', actuate: str = 'onLoad', filter_name: str | None = None, **kwargs: Any)
50    def __init__(
51        self,
52        url: str = "",
53        xlink_type: str = "simple",
54        show: str = "embed",
55        actuate: str = "onLoad",
56        filter_name: str | None = None,
57        **kwargs: Any,
58    ) -> None:
59        """Initialisation of an DrawImage.
60
61        Arguments:
62
63            url -- str
64
65            type -- str
66
67            show -- str
68
69            actuate -- str
70
71            filter_name -- str
72        """
73        super().__init__(**kwargs)
74        if self._do_init:
75            self.url = url
76            self.type = xlink_type
77            self.show = show
78            self.actuate = actuate
79            self.filter_name = filter_name

Initialisation of an DrawImage.

Arguments:

url -- str

type -- str

show -- str

actuate -- str

filter_name -- str
url: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
show: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
actuate: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
filter_name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class DrawPage(odfdo.Element):
 33class DrawPage(Element):
 34    """ODF draw page "draw:page", for pages of presentation and drawings."""
 35
 36    _tag = "draw:page"
 37    _properties = (
 38        PropDef("name", "draw:name"),
 39        PropDef("draw_id", "draw:id"),
 40        PropDef("master_page", "draw:master-page-name"),
 41        PropDef(
 42            "presentation_page_layout", "presentation:presentation-page-layout-name"
 43        ),
 44        PropDef("style", "draw:style-name"),
 45    )
 46
 47    def __init__(
 48        self,
 49        draw_id: str | None = None,
 50        name: str | None = None,
 51        master_page: str | None = None,
 52        presentation_page_layout: str | None = None,
 53        style: str | None = None,
 54        **kwargs: Any,
 55    ) -> None:
 56        """
 57        Arguments:
 58
 59            draw_id -- str
 60
 61            name -- str
 62
 63            master_page -- str
 64
 65            presentation_page_layout -- str
 66
 67            style -- str
 68        """
 69        super().__init__(**kwargs)
 70        if self._do_init:
 71            if draw_id:
 72                self.draw_id = draw_id
 73            if name:
 74                self.name = name
 75            if master_page:
 76                self.master_page = master_page
 77            if presentation_page_layout:
 78                self.presentation_page_layout = presentation_page_layout
 79            if style:
 80                self.style = style
 81
 82    def set_transition(
 83        self,
 84        smil_type: str,
 85        subtype: str | None = None,
 86        dur: str = "2s",
 87    ) -> None:
 88        # Create the new animation
 89        anim_page = AnimPar(presentation_node_type="timing-root")
 90        anim_begin = AnimPar(smil_begin=f"{self.draw_id}.begin")
 91        transition = AnimTransFilter(
 92            smil_dur=dur, smil_type=smil_type, smil_subtype=subtype
 93        )
 94        anim_page.append(anim_begin)
 95        anim_begin.append(transition)
 96
 97        # Replace when already a transition:
 98        #   anim:seq => After the frame's transition
 99        #   cf page 349 of OpenDocument-v1.0-os.pdf
100        #   Conclusion: We must delete the first child 'anim:par'
101        existing = self.get_element("anim:par")
102        if existing:
103            self.delete(existing)
104        self.append(anim_page)
105
106    def get_shapes(self) -> list[Element]:
107        query = "(descendant::" + "|descendant::".join(registered_shapes) + ")"
108        return self.get_elements(query)
109
110    def get_formatted_text(self, context: dict | None = None) -> str:
111        result: list[str] = []
112        for child in self.children:
113            if child.tag == "presentation:notes":
114                # No need for an advanced odf_notes.get_formatted_text()
115                # because the text seems to be only contained in paragraphs
116                # and frames, that we already handle
117                for sub_child in child.children:
118                    result.append(sub_child.get_formatted_text(context))
119                result.append("\n")
120            result.append(child.get_formatted_text(context))
121        result.append("\n")
122        return "".join(result)

ODF draw page "draw:page", for pages of presentation and drawings.

DrawPage( draw_id: str | None = None, name: str | None = None, master_page: str | None = None, presentation_page_layout: str | None = None, style: str | None = None, **kwargs: Any)
47    def __init__(
48        self,
49        draw_id: str | None = None,
50        name: str | None = None,
51        master_page: str | None = None,
52        presentation_page_layout: str | None = None,
53        style: str | None = None,
54        **kwargs: Any,
55    ) -> None:
56        """
57        Arguments:
58
59            draw_id -- str
60
61            name -- str
62
63            master_page -- str
64
65            presentation_page_layout -- str
66
67            style -- str
68        """
69        super().__init__(**kwargs)
70        if self._do_init:
71            if draw_id:
72                self.draw_id = draw_id
73            if name:
74                self.name = name
75            if master_page:
76                self.master_page = master_page
77            if presentation_page_layout:
78                self.presentation_page_layout = presentation_page_layout
79            if style:
80                self.style = style

Arguments:

draw_id -- str

name -- str

master_page -- str

presentation_page_layout -- str

style -- str
def set_transition( self, smil_type: str, subtype: str | None = None, dur: str = '2s') -> None:
 82    def set_transition(
 83        self,
 84        smil_type: str,
 85        subtype: str | None = None,
 86        dur: str = "2s",
 87    ) -> None:
 88        # Create the new animation
 89        anim_page = AnimPar(presentation_node_type="timing-root")
 90        anim_begin = AnimPar(smil_begin=f"{self.draw_id}.begin")
 91        transition = AnimTransFilter(
 92            smil_dur=dur, smil_type=smil_type, smil_subtype=subtype
 93        )
 94        anim_page.append(anim_begin)
 95        anim_begin.append(transition)
 96
 97        # Replace when already a transition:
 98        #   anim:seq => After the frame's transition
 99        #   cf page 349 of OpenDocument-v1.0-os.pdf
100        #   Conclusion: We must delete the first child 'anim:par'
101        existing = self.get_element("anim:par")
102        if existing:
103            self.delete(existing)
104        self.append(anim_page)
def get_shapes(self) -> list[Element]:
106    def get_shapes(self) -> list[Element]:
107        query = "(descendant::" + "|descendant::".join(registered_shapes) + ")"
108        return self.get_elements(query)
def get_formatted_text(self, context: dict | None = None) -> str:
110    def get_formatted_text(self, context: dict | None = None) -> str:
111        result: list[str] = []
112        for child in self.children:
113            if child.tag == "presentation:notes":
114                # No need for an advanced odf_notes.get_formatted_text()
115                # because the text seems to be only contained in paragraphs
116                # and frames, that we already handle
117                for sub_child in child.children:
118                    result.append(sub_child.get_formatted_text(context))
119                result.append("\n")
120            result.append(child.get_formatted_text(context))
121        result.append("\n")
122        return "".join(result)

This function should return a beautiful version of the text.

name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
draw_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
master_page: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
presentation_page_layout: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Element(odfdo.utils.cached_element.CachedElement):
 306class Element(CachedElement):
 307    """Super class of all ODF classes.
 308
 309    Representation of an XML element. Abstraction of the XML library behind.
 310    """
 311
 312    _tag: str = ""
 313    _caching: bool = False
 314    _properties: tuple[PropDef, ...] = ()
 315
 316    def __init__(self, **kwargs: Any) -> None:
 317        tag_or_elem = kwargs.pop("tag_or_elem", None)
 318        if tag_or_elem is None:
 319            # Instance for newly created object: create new lxml element and
 320            # continue by subclass __init__
 321            # If the tag key word exists, make a custom element
 322            self._do_init = True
 323            tag = kwargs.pop("tag", self._tag)
 324            self.__element = self.make_etree_element(tag)
 325        else:
 326            # called with an existing lxml element, sould be a result of
 327            # from_tag() casting, do not execute the subclass __init__
 328            if not isinstance(tag_or_elem, _Element):
 329                raise TypeError(f'"{type(tag_or_elem)}" is not an element node')
 330            self._do_init = False
 331            self.__element = tag_or_elem
 332
 333    def __repr__(self) -> str:
 334        return f"<{self.__class__.__name__} tag={self.tag}>"
 335
 336    def __str__(self) -> str:
 337        return self.text_recursive
 338
 339    @classmethod
 340    def from_tag(cls, tag_or_elem: str | _Element) -> Element:
 341        """Element class and subclass factory.
 342
 343        Turn an lxml Element or ODF string tag into an ODF XML Element
 344        of the relevant class.
 345
 346        Arguments:
 347
 348            tag_or_elem -- ODF str tag or lxml.Element
 349
 350        Return: Element (or subclass) instance
 351        """
 352        if isinstance(tag_or_elem, str):
 353            # assume the argument is a prefix:name tag
 354            elem = cls.make_etree_element(tag_or_elem)
 355        else:
 356            elem = tag_or_elem
 357        klass = _class_registry.get(elem.tag, cls)
 358        return klass(tag_or_elem=elem)
 359
 360    @classmethod
 361    def from_tag_for_clone(
 362        cls: type,
 363        tree_element: _Element,
 364        cache: tuple | None,
 365    ) -> Element:
 366        tag = to_str(tree_element.tag)
 367        klass = _class_registry.get(tag, cls)
 368        element = klass(tag_or_elem=tree_element)
 369        if cache and element._caching:
 370            element._tmap = cache[0]
 371            element._cmap = cache[1]
 372            if len(cache) == 3:
 373                element._rmap = cache[2]
 374        return element
 375
 376    @staticmethod
 377    def make_etree_element(tag: str) -> _Element:
 378        if not isinstance(tag, str):
 379            raise TypeError(f"Tag is not str: {tag!r}")
 380        tag = tag.strip()
 381        if not tag:
 382            raise ValueError("Tag is empty")
 383        if "<" not in tag:
 384            # Qualified name
 385            # XXX don't build the element from scratch or lxml will pollute with
 386            # repeated namespace declarations
 387            tag = f"<{tag}/>"
 388        # XML fragment
 389        root = fromstring(NAMESPACES_XML % str_to_bytes(tag))
 390        return root[0]
 391
 392    @staticmethod
 393    def _generic_attrib_getter(attr_name: str, family: str | None = None) -> Callable:
 394        name = _get_lxml_tag(attr_name)
 395
 396        def getter(self: Element) -> str | bool | None:
 397            try:
 398                if family and self.family != family:  # type: ignore
 399                    return None
 400            except AttributeError:
 401                return None
 402            value = self.__element.get(name)
 403            if value is None:
 404                return None
 405            elif value in ("true", "false"):
 406                return Boolean.decode(value)
 407            return str(value)
 408
 409        return getter
 410
 411    @staticmethod
 412    def _generic_attrib_setter(attr_name: str, family: str | None = None) -> Callable:
 413        name = _get_lxml_tag(attr_name)
 414
 415        def setter(self: Element, value: Any) -> None:
 416            try:
 417                if family and self.family != family:  # type: ignore
 418                    return None
 419            except AttributeError:
 420                return None
 421            if value is None:
 422                with contextlib.suppress(KeyError):
 423                    del self.__element.attrib[name]
 424                return
 425            if isinstance(value, bool):
 426                value = Boolean.encode(value)
 427            self.__element.set(name, str(value))
 428
 429        return setter
 430
 431    @classmethod
 432    def _define_attribut_property(cls: type[Element]) -> None:
 433        for prop in cls._properties:
 434            setattr(
 435                cls,
 436                prop.name,
 437                property(
 438                    cls._generic_attrib_getter(prop.attr, prop.family or None),
 439                    cls._generic_attrib_setter(prop.attr, prop.family or None),
 440                    None,
 441                    f"Get/set the attribute {prop.attr}",
 442                ),
 443            )
 444
 445    @staticmethod
 446    def _make_before_regex(
 447        before: str | None,
 448        after: str | None,
 449    ) -> re.Pattern:
 450        # 1) before xor after is not None
 451        if before is not None:
 452            return re.compile(before)
 453        else:
 454            if after is None:
 455                raise ValueError("Both 'before' and 'after' are None")
 456            return re.compile(after)
 457
 458    @staticmethod
 459    def _search_negative_position(
 460        xpath_result: list,
 461        regex: re.Pattern,
 462    ) -> tuple[str, re.Match]:
 463        # Found the last text that matches the regex
 464        text = None
 465        for a_text in xpath_result:
 466            if regex.search(str(a_text)) is not None:
 467                text = a_text
 468        if text is None:
 469            raise ValueError(f"Text not found: '{xpath_result}'")
 470        if not isinstance(text, str):
 471            raise TypeError(f"Text not found or text not of type str: '{text}'")
 472        return text, list(regex.finditer(text))[-1]
 473
 474    @staticmethod
 475    def _search_positive_position(
 476        xpath_result: list,
 477        regex: re.Pattern,
 478        position: int,
 479    ) -> tuple[str, re.Match]:
 480        # Found the last text that matches the regex
 481        count = 0
 482        for text in xpath_result:
 483            found_nb = len(regex.findall(str(text)))
 484            if found_nb + count >= position + 1:
 485                break
 486            count += found_nb
 487        else:
 488            raise ValueError(f"Text not found: '{xpath_result}'")
 489        if not isinstance(text, str):
 490            raise TypeError(f"Text not found or text not of type str: '{text}'")
 491        return text, list(regex.finditer(text))[position - count]
 492
 493    def _insert_before_after(
 494        self,
 495        current: _Element,
 496        element: _Element,
 497        before: str | None,
 498        after: str | None,
 499        position: int,
 500        xpath_text: XPath,
 501    ) -> tuple[int, str]:
 502        regex = self._make_before_regex(before, after)
 503        xpath_result = xpath_text(current)
 504        if not isinstance(xpath_result, list):
 505            raise TypeError("Bad XPath result")
 506        # position = -1
 507        if position < 0:
 508            text, sre = self._search_negative_position(xpath_result, regex)
 509        # position >= 0
 510        else:
 511            text, sre = self._search_positive_position(xpath_result, regex, position)
 512        # Compute pos
 513        if before is None:
 514            pos = sre.end()
 515        else:
 516            pos = sre.start()
 517        return pos, text
 518
 519    def _insert_find_text(
 520        self,
 521        current: _Element,
 522        element: _Element,
 523        before: str | None,
 524        after: str | None,
 525        position: int,
 526        xpath_text: XPath,
 527    ) -> tuple[int, str]:
 528        # Find the text
 529        xpath_result = xpath_text(current)
 530        if not isinstance(xpath_result, list):
 531            raise TypeError("Bad XPath result")
 532        count = 0
 533        for text in xpath_result:
 534            if not isinstance(text, str):
 535                continue
 536            found_nb = len(text)
 537            if found_nb + count >= position:
 538                break
 539            count += found_nb
 540        else:
 541            raise ValueError("Text not found")
 542        # We insert before the character
 543        pos = position - count
 544        return pos, text
 545
 546    def _insert(
 547        self,
 548        element: Element,
 549        before: str | None = None,
 550        after: str | None = None,
 551        position: int = 0,
 552        main_text: bool = False,
 553    ) -> None:
 554        """Insert an element before or after the characters in the text which
 555        match the regex before/after.
 556
 557        When the regex matches more of one part of the text, position can be
 558        set to choice which part must be used. If before and after are None,
 559        we use only position that is the number of characters. If position is
 560        positive and before=after=None, we insert before the position
 561        character. But if position=-1, we insert after the last character.
 562
 563
 564        Arguments:
 565
 566        element -- Element
 567
 568        before -- str regex
 569
 570        after -- str regex
 571
 572        position -- int
 573        """
 574        # not implemented: if main_text is True, filter out the annotations texts in computation.
 575        current = self.__element
 576        xelement = element.__element
 577
 578        if main_text:
 579            xpath_text = _xpath_text_main_descendant
 580        else:
 581            xpath_text = _xpath_text_descendant
 582
 583        # 1) before xor after is not None
 584        if (before is not None) ^ (after is not None):
 585            pos, text = self._insert_before_after(
 586                current,
 587                xelement,
 588                before,
 589                after,
 590                position,
 591                xpath_text,
 592            )
 593        # 2) before=after=None => only with position
 594        elif before is None and after is None:
 595            # Hack if position is negative => quickly
 596            if position < 0:
 597                current.append(xelement)
 598                return
 599            pos, text = self._insert_find_text(
 600                current,
 601                xelement,
 602                before,
 603                after,
 604                position,
 605                xpath_text,
 606            )
 607        else:
 608            raise ValueError("bad combination of arguments")
 609
 610        # Compute new texts
 611        text_before = text[:pos] if text[:pos] else None
 612        text_after = text[pos:] if text[pos:] else None
 613
 614        # Insert!
 615        parent = text.getparent()  # type: ignore
 616        if text.is_text:  # type: ignore
 617            parent.text = text_before
 618            element.tail = text_after
 619            parent.insert(0, xelement)
 620        else:
 621            parent.addnext(xelement)
 622            parent.tail = text_before
 623            element.tail = text_after
 624
 625    def _insert_between(  # noqa: C901
 626        self,
 627        element: Element,
 628        from_: str,
 629        to: str,
 630    ) -> None:
 631        """Insert the given empty element to wrap the text beginning with
 632        "from_" and ending with "to".
 633
 634        Example 1: '<p>toto tata titi</p>
 635
 636        We want to insert a link around "tata".
 637
 638        Result 1: '<p>toto <a>tata</a> titi</p>
 639
 640        Example 2: '<p><span>toto</span> tata titi</p>
 641
 642        We want to insert a link around "tata".
 643
 644        Result 2: '<p><span>toto</span> <a>tata</a> titi</p>
 645
 646        Example 3: '<p>toto <span> tata </span> titi</p>'
 647
 648        We want to insert a link from "tata" to "titi" included.
 649
 650        Result 3: '<p>toto <span> </span>'
 651                  '<a><span>tata </span> titi</a></p>'
 652
 653        Example 4: '<p>toto <span>tata titi</span> tutu</p>'
 654
 655        We want to insert a link from "titi" to "tutu"
 656
 657        Result 4: '<p>toto <span>tata </span><a><span>titi</span></a>'
 658                  '<a> tutu</a></p>'
 659
 660        Example 5: '<p>toto <span>tata titi</span> '
 661                   '<span>tutu tyty</span></p>'
 662
 663        We want to insert a link from "titi" to "tutu"
 664
 665        Result 5: '<p>toto <span>tata </span><a><span>titi</span><a> '
 666                  '<a> <span>tutu</span></a><span> tyty</span></p>'
 667        """
 668        current = self.__element
 669        wrapper = element.__element
 670
 671        xpath_result = _xpath_text_descendant(current)
 672        if not isinstance(xpath_result, list):
 673            raise TypeError("Bad XPath result")
 674
 675        for text in xpath_result:
 676            if not isinstance(text, str):
 677                raise TypeError("Text not found or text not of type str")
 678            if from_ not in text:
 679                continue
 680            from_index = text.index(from_)
 681            text_before = text[:from_index]
 682            text_after = text[from_index:]
 683            from_container = text.getparent()  # type: ignore
 684            if not isinstance(from_container, _Element):
 685                raise TypeError("Bad XPath result")
 686            # Include from_index to match a single word
 687            to_index = text.find(to, from_index)
 688            if to_index >= 0:
 689                # Simple case: "from" and "to" in the same element
 690                to_end = to_index + len(to)
 691                if text.is_text:  # type: ignore
 692                    from_container.text = text_before
 693                    wrapper.text = text[to_index:to_end]
 694                    wrapper.tail = text[to_end:]
 695                    from_container.insert(0, wrapper)
 696                else:
 697                    from_container.tail = text_before
 698                    wrapper.text = text[to_index:to_end]
 699                    wrapper.tail = text[to_end:]
 700                    parent = from_container.getparent()
 701                    index = parent.index(from_container)  # type: ignore
 702                    parent.insert(index + 1, wrapper)  # type: ignore
 703                return
 704            else:
 705                # Exit to the second part where we search for the end text
 706                break
 707        else:
 708            raise ValueError("Start text not found")
 709
 710        # The container is split in two
 711        container2 = deepcopy(from_container)
 712        if text.is_text:  # type: ignore
 713            from_container.text = text_before
 714            from_container.tail = None
 715            container2.text = text_after
 716            from_container.tail = None
 717        else:
 718            from_container.tail = text_before
 719            container2.tail = text_after
 720        # Stack the copy into the surrounding element
 721        wrapper.append(container2)
 722        parent = from_container.getparent()
 723        index = parent.index(from_container)  # type: ignore
 724        parent.insert(index + 1, wrapper)  # type: ignore
 725
 726        xpath_result = _xpath_text_descendant(wrapper)
 727        if not isinstance(xpath_result, list):
 728            raise TypeError("Bad XPath result")
 729
 730        for text in xpath_result:
 731            if not isinstance(text, str):
 732                raise TypeError("Text not found or text not of type str")
 733            if to not in text:
 734                continue
 735            to_end = text.index(to) + len(to)
 736            text_before = text[:to_end]
 737            text_after = text[to_end:]
 738            container_to = text.getparent()  # type: ignore
 739            if not isinstance(container_to, _Element):
 740                raise TypeError("Bad XPath result")
 741            if text.is_text:  # type: ignore
 742                container_to.text = text_before
 743                container_to.tail = text_after
 744            else:
 745                container_to.tail = text_before
 746                next_one = container_to.getnext()
 747                if next_one is None:
 748                    next_one = container_to.getparent()
 749                next_one.tail = text_after  # type: ignore
 750            return
 751        raise ValueError("End text not found")
 752
 753    @property
 754    def tag(self) -> str:
 755        """Get/set the underlying xml tag with the given qualified name.
 756
 757        Warning: direct change of tag does not change the element class.
 758
 759        Arguments:
 760
 761            qname -- str (e.g. "text:span")
 762        """
 763        return _get_prefixed_name(self.__element.tag)
 764
 765    @tag.setter
 766    def tag(self, qname: str) -> None:
 767        self.__element.tag = _get_lxml_tag(qname)
 768
 769    def elements_repeated_sequence(
 770        self,
 771        xpath_instance: XPath,
 772        name: str,
 773    ) -> list[tuple[int, int]]:
 774        """Utility method for table module."""
 775        lxml_tag = _get_lxml_tag_or_name(name)
 776        element = self.__element
 777        sub_elements = xpath_instance(element)
 778        if not isinstance(sub_elements, list):
 779            raise TypeError("Bad XPath result.")
 780        result: list[tuple[int, int]] = []
 781        idx = -1
 782        for sub_element in sub_elements:
 783            if not isinstance(sub_element, _Element):
 784                continue
 785            idx += 1
 786            value = sub_element.get(lxml_tag)
 787            if value is None:
 788                result.append((idx, 1))
 789                continue
 790            try:
 791                int_value = int(value)
 792            except ValueError:
 793                int_value = 1
 794            result.append((idx, max(int_value, 1)))
 795        return result
 796
 797    def get_elements(self, xpath_query: XPath | str) -> list[Element]:
 798        cache: tuple | None = None
 799        element = self.__element
 800        if isinstance(xpath_query, str):
 801            new_xpath_query = xpath_compile(xpath_query)
 802            result = new_xpath_query(element)
 803        else:
 804            result = xpath_query(element)
 805        if not isinstance(result, list):
 806            raise TypeError("Bad XPath result")
 807
 808        if hasattr(self, "_tmap"):
 809            if hasattr(self, "_rmap"):
 810                cache = (self._tmap, self._cmap, self._rmap)
 811            else:
 812                cache = (self._tmap, self._cmap)
 813        return [
 814            Element.from_tag_for_clone(e, cache)
 815            for e in result
 816            if isinstance(e, _Element)
 817        ]
 818
 819    # fixme : need original get_element as wrapper of get_elements
 820
 821    def get_element(self, xpath_query: XPath | str) -> Element | None:
 822        element = self.__element
 823        result = element.xpath(f"({xpath_query})[1]", namespaces=ODF_NAMESPACES)
 824        if result:
 825            return Element.from_tag(result[0])  # type:ignore
 826        return None
 827
 828    def _get_element_idx(self, xpath_query: XPath | str, idx: int) -> Element | None:
 829        element = self.__element
 830        result = element.xpath(f"({xpath_query})[{idx + 1}]", namespaces=ODF_NAMESPACES)
 831        if result:
 832            return Element.from_tag(result[0])  # type:ignore
 833        return None
 834
 835    def _get_element_idx2(self, xpath_instance: XPath, idx: int) -> Element | None:
 836        element = self.__element
 837        result = xpath_instance(element, idx=idx + 1)
 838        if result:
 839            return Element.from_tag(result[0])  # type:ignore
 840        return None
 841
 842    @property
 843    def attributes(self) -> dict[str, str]:
 844        return {
 845            _get_prefixed_name(str(key)): str(value)
 846            for key, value in self.__element.attrib.items()
 847        }
 848
 849    def get_attribute(self, name: str) -> str | bool | None:
 850        """Return the attribute value as type str | bool | None."""
 851        element = self.__element
 852        lxml_tag = _get_lxml_tag_or_name(name)
 853        value = element.get(lxml_tag)
 854        if value is None:
 855            return None
 856        elif value in ("true", "false"):
 857            return Boolean.decode(value)
 858        return str(value)
 859
 860    def get_attribute_integer(self, name: str) -> int | None:
 861        """Return either the attribute as type int, or None."""
 862        element = self.__element
 863        lxml_tag = _get_lxml_tag_or_name(name)
 864        value = element.get(lxml_tag)
 865        if value is None:
 866            return None
 867        try:
 868            return int(value)
 869        except ValueError:
 870            return None
 871
 872    def get_attribute_string(self, name: str) -> str | None:
 873        """Return either the attribute as type str, or None."""
 874        element = self.__element
 875        lxml_tag = _get_lxml_tag_or_name(name)
 876        value = element.get(lxml_tag)
 877        if value is None:
 878            return None
 879        return str(value)
 880
 881    def set_attribute(
 882        self, name: str, value: bool | str | tuple[int, int, int] | None
 883    ) -> None:
 884        if name in ODF_COLOR_PROPERTY:
 885            if isinstance(value, bool):
 886                raise TypeError(f"Wrong color type {value!r}")
 887            if value != "transparent":
 888                value = hexa_color(value)
 889        element = self.__element
 890        lxml_tag = _get_lxml_tag_or_name(name)
 891        if isinstance(value, bool):
 892            value = Boolean.encode(value)
 893        elif value is None:
 894            with contextlib.suppress(KeyError):
 895                del element.attrib[lxml_tag]
 896            return
 897        element.set(lxml_tag, str(value))
 898
 899    def set_style_attribute(self, name: str, value: Element | str) -> None:
 900        """Shortcut to accept a style object as a value."""
 901        if isinstance(value, Element):
 902            value = str(value.name)  # type:ignore
 903        return self.set_attribute(name, value)
 904
 905    def del_attribute(self, name: str) -> None:
 906        element = self.__element
 907        lxml_tag = _get_lxml_tag_or_name(name)
 908        del element.attrib[lxml_tag]
 909
 910    @property
 911    def text(self) -> str:
 912        """Get / set the text content of the element."""
 913        return self.__element.text or ""
 914
 915    @text.setter
 916    def text(self, text: str | None) -> None:
 917        if text is None:
 918            text = ""
 919        try:
 920            self.__element.text = text
 921        except TypeError as e:
 922            raise TypeError(f'Str type expected: "{type(text)}"') from e
 923
 924    @property
 925    def text_recursive(self) -> str:
 926        return "".join(str(x) for x in self.__element.itertext())
 927
 928    @property
 929    def tail(self) -> str | None:
 930        """Get / set the text immediately following the element."""
 931        return self.__element.tail
 932
 933    @tail.setter
 934    def tail(self, text: str | None) -> None:
 935        self.__element.tail = text or ""
 936
 937    def search(self, pattern: str) -> int | None:
 938        """Return the first position of the pattern in the text content of
 939        the element, or None if not found.
 940
 941        Python regular expression syntax applies.
 942
 943        Arguments:
 944
 945            pattern -- str
 946
 947        Return: int or None
 948        """
 949        match = re.search(pattern, self.text_recursive)
 950        if match is None:
 951            return None
 952        return match.start()
 953
 954    def match(self, pattern: str) -> bool:
 955        """return True if the pattern is found one or more times anywhere in
 956        the text content of the element.
 957
 958        Python regular expression syntax applies.
 959
 960        Arguments:
 961
 962            pattern -- str
 963
 964        Return: bool
 965        """
 966        return self.search(pattern) is not None
 967
 968    def replace(self, pattern: str, new: str | None = None) -> int:
 969        """Replace the pattern with the given text, or delete if text is an
 970        empty string, and return the number of replacements. By default, only
 971        return the number of occurences that would be replaced.
 972
 973        It cannot replace patterns found across several element, like a word
 974        split into two consecutive spans.
 975
 976        Python regular expression syntax applies.
 977
 978        Arguments:
 979
 980            pattern -- str
 981
 982            new -- str
 983
 984        Return: int
 985        """
 986        if not isinstance(pattern, str):
 987            # Fail properly if the pattern is an non-ascii bytestring
 988            pattern = str(pattern)
 989        cpattern = re.compile(pattern)
 990        count = 0
 991        for text in self.xpath("descendant::text()"):
 992            if new is None:
 993                count += len(cpattern.findall(str(text)))
 994            else:
 995                new_text, number = cpattern.subn(new, str(text))
 996                container = text.parent
 997                if text.is_text():  # type: ignore
 998                    container.text = new_text  # type: ignore
 999                else:
1000                    container.tail = new_text  # type: ignore
1001                count += number
1002        return count
1003
1004    @property
1005    def root(self) -> Element:
1006        element = self.__element
1007        tree = element.getroottree()
1008        root = tree.getroot()
1009        return Element.from_tag(root)
1010
1011    @property
1012    def parent(self) -> Element | None:
1013        element = self.__element
1014        parent = element.getparent()
1015        if parent is None:
1016            # Already at root
1017            return None
1018        return Element.from_tag(parent)
1019
1020    @property
1021    def is_bound(self) -> bool:
1022        return self.parent is not None
1023
1024    # def get_next_sibling(self):
1025    #     element = self.__element
1026    #     next_one = element.getnext()
1027    #     if next_one is None:
1028    #         return None
1029    #     return Element.from_tag(next_one)
1030    #
1031    # def get_prev_sibling(self):
1032    #     element = self.__element
1033    #     prev = element.getprevious()
1034    #     if prev is None:
1035    #         return None
1036    #     return Element.from_tag(prev)
1037
1038    @property
1039    def children(self) -> list[Element]:
1040        element = self.__element
1041        return [
1042            Element.from_tag(e)
1043            for e in element.iterchildren()
1044            if isinstance(e, _Element)
1045        ]
1046
1047    def index(self, child: Element) -> int:
1048        """Return the position of the child in this element.
1049
1050        Inspired by lxml
1051        """
1052        return self.__element.index(child.__element)
1053
1054    @property
1055    def text_content(self) -> str:
1056        """Get / set the text of the embedded paragraph, including embeded
1057        annotations, cells...
1058
1059        Set create a paragraph if missing
1060        """
1061        return "\n".join(
1062            child.text_recursive for child in self.get_elements("descendant::text:p")
1063        )
1064
1065    @text_content.setter
1066    def text_content(self, text: str | None) -> None:
1067        paragraphs = self.get_elements("text:p")
1068        if not paragraphs:
1069            # E.g., text:p in draw:text-box in draw:frame
1070            paragraphs = self.get_elements("*/text:p")
1071        if paragraphs:
1072            paragraph = paragraphs.pop(0)
1073            for obsolete in paragraphs:
1074                obsolete.delete()
1075        else:
1076            paragraph = Element.from_tag("text:p")
1077            self.insert(paragraph, FIRST_CHILD)
1078        # As "text_content" returned all text nodes, "text_content"
1079        # will overwrite all text nodes and children that may contain them
1080        element = paragraph.__element
1081        # Clear but the attributes
1082        del element[:]
1083        element.text = text
1084
1085    def _erase_text_content(self) -> None:
1086        paragraphs = self.get_elements("text:p")
1087        if not paragraphs:
1088            # E.g., text:p in draw:text-box in draw:frame
1089            paragraphs = self.get_elements("*/text:p")
1090        if paragraphs:
1091            paragraphs.pop(0)
1092            for obsolete in paragraphs:
1093                obsolete.delete()
1094
1095    def is_empty(self) -> bool:
1096        """Check if the element is empty : no text, no children, no tail.
1097
1098        Return: Boolean
1099        """
1100        element = self.__element
1101        if element.tail is not None:
1102            return False
1103        if element.text is not None:
1104            return False
1105        if list(element.iterchildren()):
1106            return False
1107        return True
1108
1109    def _get_successor(self, target: Element) -> tuple[Element | None, Element | None]:
1110        element = self.__element
1111        next_one = element.getnext()
1112        if next_one is not None:
1113            return Element.from_tag(next_one), target
1114        parent = self.parent
1115        if parent is None:
1116            return None, None
1117        return parent._get_successor(target.parent)  # type:ignore
1118
1119    def _get_between_base(  # noqa:C901
1120        self,
1121        tag1: Element,
1122        tag2: Element,
1123    ) -> list[Element]:
1124        def find_any_id(elem: Element) -> tuple[str, str, str]:
1125            elem_tag = elem.tag
1126            for attribute in (
1127                "text:id",
1128                "text:change-id",
1129                "text:name",
1130                "office:name",
1131                "text:ref-name",
1132                "xml:id",
1133            ):
1134                idx = elem.get_attribute(attribute)
1135                if idx is not None:
1136                    return elem_tag, attribute, str(idx)
1137            raise ValueError(f"No Id found in {elem.serialize()}")
1138
1139        def common_ancestor(
1140            tag1: str,
1141            attr1: str,
1142            val1: str,
1143            tag2: str,
1144            attr2: str,
1145            val2: str,
1146        ) -> Element | None:
1147            root = self.root
1148            request1 = f'descendant::{tag1}[@{attr1}="{val1}"]'
1149            request2 = f'descendant::{tag2}[@{attr2}="{val2}"]'
1150            ancestor = root.xpath(request1)[0]
1151            if ancestor is None:
1152                return None
1153            while True:
1154                # print "up",
1155                new_ancestor = ancestor.parent
1156                if new_ancestor is None:
1157                    return None
1158                has_tag2 = new_ancestor.xpath(request2)
1159                ancestor = new_ancestor
1160                if not has_tag2:
1161                    continue
1162                # print 'found'
1163                break
1164            # print up.serialize()
1165            return ancestor
1166
1167        elem1_tag, elem1_attr, elem1_val = find_any_id(tag1)
1168        elem2_tag, elem2_attr, elem2_val = find_any_id(tag2)
1169        ancestor_result = common_ancestor(
1170            elem1_tag,
1171            elem1_attr,
1172            elem1_val,
1173            elem2_tag,
1174            elem2_attr,
1175            elem2_val,
1176        )
1177        if ancestor_result is None:
1178            raise RuntimeError(f"No common ancestor for {elem1_tag} {elem2_tag}")
1179        ancestor = ancestor_result.clone
1180        path1 = f'{elem1_tag}[@{elem1_attr}="{elem1_val}"]'
1181        path2 = f'{elem2_tag}[@{elem2_attr}="{elem2_val}"]'
1182        result = ancestor.clone
1183        for child in result.children:
1184            result.delete(child)
1185        result.text = ""
1186        result.tail = ""
1187        target = result
1188        current = ancestor.children[0]
1189
1190        state = 0
1191        while True:
1192            if current is None:
1193                raise RuntimeError(f"No current ancestor for {elem1_tag} {elem2_tag}")
1194            # print 'current', state, current.serialize()
1195            if state == 0:  # before tag 1
1196                if current.xpath(f"descendant-or-self::{path1}"):
1197                    if current.xpath(f"self::{path1}"):
1198                        tail = current.tail
1199                        if tail:
1200                            # got a tail => the parent should be either t:p or t:h
1201                            target.text = tail  # type: ignore
1202                        current, target = current._get_successor(target)  # type: ignore
1203                        state = 1
1204                        continue
1205                    # got T1 in chidren, need further analysis
1206                    new_target = current.clone
1207                    for child in new_target.children:
1208                        new_target.delete(child)
1209                    new_target.text = ""
1210                    new_target.tail = ""
1211                    target.append(new_target)  # type: ignore
1212                    target = new_target
1213                    current = current.children[0]
1214                    continue
1215                else:
1216                    # before tag1 : forget element, go to next one
1217                    current, target = current._get_successor(target)  # type: ignore
1218                    continue
1219            elif state == 1:  # collect elements
1220                further = False
1221                if current.xpath(f"descendant-or-self::{path2}"):
1222                    if current.xpath(f"self::{path2}"):
1223                        # end of trip
1224                        break
1225                    # got T2 in chidren, need further analysis
1226                    further = True
1227                # further analysis needed :
1228                if further:
1229                    new_target = current.clone
1230                    for child in new_target.children:
1231                        new_target.delete(child)
1232                    new_target.text = ""
1233                    new_target.tail = ""
1234                    target.append(new_target)  # type: ignore
1235                    target = new_target
1236                    current = current.children[0]
1237                    continue
1238                # collect
1239                target.append(current.clone)  # type: ignore
1240                current, target = current._get_successor(target)  # type: ignore
1241                continue
1242        # Now resu should be the "parent" of inserted parts
1243        # - a text:h or text:p sigle item (simple case)
1244        # - a upper element, with some text:p, text:h in it => need to be
1245        #   stripped to have a list of text:p, text:h
1246        if result.tag in {"text:p", "text:h"}:
1247            inner = [result]
1248        else:
1249            inner = result.children
1250        return inner
1251
1252    def get_between(
1253        self,
1254        tag1: Element,
1255        tag2: Element,
1256        as_text: bool = False,
1257        clean: bool = True,
1258        no_header: bool = True,
1259    ) -> list | Element | str:
1260        """Returns elements between tag1 and tag2, tag1 and tag2 shall
1261        be unique and having an id attribute.
1262        (WARN: buggy if tag1/tag2 defines a malformed odf xml.)
1263        If as_text is True: returns the text content.
1264        If clean is True: suppress unwanted tags (deletions marks, ...)
1265        If no_header is True: existing text:h are changed in text:p
1266        By default: returns a list of Element, cleaned and without headers.
1267
1268        Implementation and standard retrictions:
1269        Only text:h and text:p sould be 'cut' by an insert tag, so inner parts
1270        of insert tags are:
1271
1272            - any text:h, text:p or sub tag of these
1273
1274            - some text, part of a parent text:h or text:p
1275
1276        Arguments:
1277
1278            tag1 -- Element
1279
1280            tag2 -- Element
1281
1282            as_text -- boolean
1283
1284            clean -- boolean
1285
1286            no_header -- boolean
1287
1288        Return: list of odf_paragraph or odf_header
1289        """
1290        inner = self._get_between_base(tag1, tag2)
1291
1292        if clean:
1293            clean_tags = (
1294                "text:change",
1295                "text:change-start",
1296                "text:change-end",
1297                "text:reference-mark",
1298                "text:reference-mark-start",
1299                "text:reference-mark-end",
1300            )
1301            request_self = " | ".join(["self::%s" % c for c in clean_tags])
1302            inner = [e for e in inner if not e.xpath(request_self)]
1303            request = " | ".join([f"descendant::{tag}" for tag in clean_tags])
1304            for element in inner:
1305                to_del = element.xpath(request)
1306                for elem in to_del:
1307                    if isinstance(elem, Element):
1308                        element.delete(elem)
1309        if no_header:  # crude replace t:h by t:p
1310            new_inner = []
1311            for element in inner:
1312                if element.tag == "text:h":
1313                    children = element.children
1314                    text = element.__element.text
1315                    para = Element.from_tag("text:p")
1316                    para.text = text or ""
1317                    for c in children:
1318                        para.append(c)
1319                    new_inner.append(para)
1320                else:
1321                    new_inner.append(element)
1322            inner = new_inner
1323        if as_text:
1324            return "\n".join([e.get_formatted_text() for e in inner])
1325        else:
1326            return inner
1327
1328    def insert(
1329        self,
1330        element: Element,
1331        xmlposition: int | None = None,
1332        position: int | None = None,
1333        start: bool = False,
1334    ) -> None:
1335        """Insert an element relatively to ourself.
1336
1337        Insert either using DOM vocabulary or by numeric position.
1338        If text start is True, insert the element before any existing text.
1339
1340        Position start at 0.
1341
1342        Arguments:
1343
1344            element -- Element
1345
1346            xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
1347                           or PREV_SIBLING
1348
1349            start -- Boolean
1350
1351            position -- int
1352        """
1353        # child_tag = element.tag
1354        current = self.__element
1355        _element = element.__element
1356        if start:
1357            text = current.text
1358            if text is not None:
1359                current.text = None
1360                tail = _element.tail
1361                if tail is None:
1362                    tail = text
1363                else:
1364                    tail = tail + text
1365                _element.tail = tail
1366            position = 0
1367        if position is not None:
1368            current.insert(position, _element)
1369        elif xmlposition is FIRST_CHILD:
1370            current.insert(0, _element)
1371        elif xmlposition is LAST_CHILD:
1372            current.append(_element)
1373        elif xmlposition is NEXT_SIBLING:
1374            parent = current.getparent()
1375            index = parent.index(current)  # type: ignore
1376            parent.insert(index + 1, _element)  # type: ignore
1377        elif xmlposition is PREV_SIBLING:
1378            parent = current.getparent()
1379            index = parent.index(current)  # type: ignore
1380            parent.insert(index, _element)  # type: ignore
1381        else:
1382            raise ValueError("(xml)position must be defined")
1383
1384    def extend(self, odf_elements: Iterable[Element]) -> None:
1385        """Fast append elements at the end of ourself using extend."""
1386        if odf_elements:
1387            current = self.__element
1388            elements = [element.__element for element in odf_elements]
1389            current.extend(elements)
1390
1391    def append(self, str_or_element: str | Element) -> None:
1392        """Insert element or text in the last position."""
1393        current = self.__element
1394        if isinstance(str_or_element, str):
1395            # Has children ?
1396            children = list(current.iterchildren())
1397            if children:
1398                # Append to tail of the last child
1399                last_child = children[-1]
1400                text = last_child.tail
1401                text = text if text is not None else ""
1402                text += str_or_element
1403                last_child.tail = text
1404            else:
1405                # Append to text of the element
1406                text = current.text
1407                text = text if text is not None else ""
1408                text += str_or_element
1409                current.text = text
1410        elif isinstance(str_or_element, Element):
1411            current.append(str_or_element.__element)
1412        else:
1413            raise TypeError(f'Element or string expected, not "{type(str_or_element)}"')
1414
1415    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
1416        """Delete the given element from the XML tree. If no element is given,
1417        "self" is deleted. The XML library may allow to continue to use an
1418        element now "orphan" as long as you have a reference to it.
1419
1420        if keep_tail is True (default), the tail text is not erased.
1421
1422        Arguments:
1423
1424            child -- Element
1425
1426            keep_tail -- boolean (default to True), True for most usages.
1427        """
1428        if child is None:
1429            parent = self.parent
1430            if parent is None:
1431                raise ValueError(f"Can't delete the root element\n{self.serialize()}")
1432            child = self
1433        else:
1434            parent = self
1435        if keep_tail and child.__element.tail is not None:
1436            current = child.__element
1437            tail = str(current.tail)
1438            current.tail = None
1439            prev = current.getprevious()
1440            if prev is not None:
1441                if prev.tail is None:
1442                    prev.tail = tail
1443                else:
1444                    prev.tail += tail
1445            else:
1446                if parent.__element.text is None:
1447                    parent.__element.text = tail
1448                else:
1449                    parent.__element.text += tail
1450        parent.__element.remove(child.__element)
1451
1452    def replace_element(self, old_element: Element, new_element: Element) -> None:
1453        """Replaces in place a sub element with the element passed as second
1454        argument.
1455
1456        Warning : no clone for old element.
1457        """
1458        current = self.__element
1459        current.replace(old_element.__element, new_element.__element)
1460
1461    def strip_elements(
1462        self,
1463        sub_elements: Element | Iterable[Element],
1464    ) -> Element | list:
1465        """Remove the tags of provided elements, keeping inner childs and text.
1466
1467        Return : the striped element.
1468
1469        Warning : no clone in sub_elements list.
1470
1471        Arguments:
1472
1473            sub_elements -- Element or list of Element
1474        """
1475        if not sub_elements:
1476            return self
1477        if isinstance(sub_elements, Element):
1478            sub_elements = (sub_elements,)
1479        replacer = _get_lxml_tag("text:this-will-be-removed")
1480        for element in sub_elements:
1481            element.__element.tag = replacer
1482        strip = ("text:this-will-be-removed",)
1483        return self.strip_tags(strip=strip, default=None)
1484
1485    def strip_tags(
1486        self,
1487        strip: Iterable[str] | None = None,
1488        protect: Iterable[str] | None = None,
1489        default: str | None = "text:p",
1490    ) -> Element | list:
1491        """Remove the tags listed in strip, recursively, keeping inner childs
1492        and text. Tags listed in protect stop the removal one level depth. If
1493        the first level element is stripped, default is used to embed the
1494        content in the default element. If default is None and first level is
1495        striped, a list of text and children is returned. Return : the striped
1496        element.
1497
1498        strip_tags should be used by on purpose methods (strip_span ...)
1499        (Method name taken from lxml).
1500
1501        Arguments:
1502
1503            strip -- iterable list of str odf tags, or None
1504
1505            protect -- iterable list of str odf tags, or None
1506
1507            default -- str odf tag, or None
1508
1509        Return:
1510
1511            Element.
1512        """
1513        if not strip:
1514            return self
1515        if not protect:
1516            protect = ()
1517        protected = False
1518        element, modified = Element._strip_tags(self, strip, protect, protected)
1519        if modified and isinstance(element, list) and default:
1520            new = Element.from_tag(default)
1521            for content in element:
1522                if isinstance(content, Element):
1523                    new.append(content)
1524                else:
1525                    new.text = content
1526            element = new
1527        return element
1528
1529    @staticmethod
1530    def _strip_tags(  # noqa:C901
1531        element: Element,
1532        strip: Iterable[str],
1533        protect: Iterable[str],
1534        protected: bool,
1535    ) -> tuple[Element | list, bool]:
1536        """Sub method for strip_tags()."""
1537        element_clone = element.clone
1538        modified = False
1539        children = []
1540        if protect and element.tag in protect:
1541            protect_below = True
1542        else:
1543            protect_below = False
1544        for child in element_clone.children:
1545            striped_child, is_modified = Element._strip_tags(
1546                child, strip, protect, protect_below
1547            )
1548            if is_modified:
1549                modified = True
1550            if isinstance(striped_child, list):
1551                children.extend(striped_child)
1552            else:
1553                children.append(striped_child)
1554
1555        text = element_clone.text
1556        tail = element_clone.tail
1557        if not protected and strip and element.tag in strip:
1558            element_result: list[Element | str] = []
1559            if text is not None:
1560                element_result.append(text)
1561            for child in children:
1562                element_result.append(child)
1563            if tail is not None:
1564                element_result.append(tail)
1565            return (element_result, True)
1566        else:
1567            if not modified:
1568                return (element, False)
1569            element.clear()
1570            try:
1571                for key, value in element_clone.attributes.items():
1572                    element.set_attribute(key, value)
1573            except ValueError:
1574                sys.stderr.write(f"strip_tags(): bad attribute in {element_clone}\n")
1575            if text is not None:
1576                element.append(text)
1577            for child in children:
1578                element.append(child)
1579            if tail is not None:
1580                element.tail = tail
1581            return (element, True)
1582
1583    def xpath(self, xpath_query: str) -> list[Element | Text]:
1584        """Apply XPath query to the element and its subtree. Return list of
1585        Element or Text instances translated from the nodes found.
1586        """
1587        element = self.__element
1588        xpath_instance = xpath_compile(xpath_query)
1589        elements = xpath_instance(element)
1590        result: list[Element | Text] = []
1591        if hasattr(elements, "__iter__"):
1592            for obj in elements:  # type: ignore
1593                if isinstance(obj, (_ElementStringResult, _ElementUnicodeResult)):
1594                    result.append(Text(obj))
1595                elif isinstance(obj, _Element):
1596                    result.append(Element.from_tag(obj))
1597                # else:
1598                #     result.append(obj)
1599        return result
1600
1601    def clear(self) -> None:
1602        """Remove text, children and attributes from the element."""
1603        self.__element.clear()
1604        if hasattr(self, "_tmap"):
1605            self._tmap: list[int] = []
1606        if hasattr(self, "_cmap"):
1607            self._cmap: list[int] = []
1608        if hasattr(self, "_rmap"):
1609            self._rmap: list[int] = []
1610        if hasattr(self, "_indexes"):
1611            remember = False
1612            if "_rmap" in self._indexes:
1613                remember = True
1614            self._indexes: dict[str, dict] = {}
1615            self._indexes["_cmap"] = {}
1616            self._indexes["_tmap"] = {}
1617            if remember:
1618                self._indexes["_rmap"] = {}
1619
1620    @property
1621    def clone(self) -> Element:
1622        clone = deepcopy(self.__element)
1623        root = lxml_Element("ROOT", nsmap=ODF_NAMESPACES)
1624        root.append(clone)
1625        return self.from_tag(clone)
1626
1627        # slow data = tostring(self.__element, encoding='unicode')
1628        # return self.from_tag(data)
1629
1630    @staticmethod
1631    def _strip_namespaces(data: str) -> str:
1632        """Remove xmlns:* fields from serialized XML."""
1633        return re.sub(r' xmlns:\w*="[\w:\-\/\.#]*"', "", data)
1634
1635    def serialize(self, pretty: bool = False, with_ns: bool = False) -> str:
1636        """Return text serialization of XML element."""
1637        # This copy bypasses serialization side-effects in lxml
1638        native = deepcopy(self.__element)
1639        data = tostring(
1640            native, with_tail=False, pretty_print=pretty, encoding="unicode"
1641        )
1642        if with_ns:
1643            return data
1644        # Remove namespaces
1645        return self._strip_namespaces(data)
1646
1647    # Element helpers usable from any context
1648
1649    @property
1650    def document_body(self) -> Element | None:
1651        """Return the document body : 'office:body'"""
1652        return self.get_element("//office:body/*[1]")
1653
1654    @document_body.setter
1655    def document_body(self, new_body: Element) -> None:
1656        """Change in place the full document body content."""
1657        body = self.document_body
1658        if body is None:
1659            raise ValueError("//office:body not found in document")
1660        tail = body.tail
1661        body.clear()
1662        for item in new_body.children:
1663            body.append(item)
1664        if tail:
1665            body.tail = tail
1666
1667    def get_formatted_text(self, context: dict | None = None) -> str:
1668        """This function should return a beautiful version of the text."""
1669        return ""
1670
1671    def get_styled_elements(self, name: str = "") -> list[Element]:
1672        """Brute-force to find paragraphs, tables, etc. using the given style
1673        name (or all by default).
1674
1675        Arguments:
1676
1677            name -- str
1678
1679        Return: list
1680        """
1681        # FIXME incomplete (and possibly inaccurate)
1682        return (
1683            self._filtered_elements("descendant::*", text_style=name)
1684            + self._filtered_elements("descendant::*", draw_style=name)
1685            + self._filtered_elements("descendant::*", draw_text_style=name)
1686            + self._filtered_elements("descendant::*", table_style=name)
1687            + self._filtered_elements("descendant::*", page_layout=name)
1688            + self._filtered_elements("descendant::*", master_page=name)
1689            + self._filtered_elements("descendant::*", parent_style=name)
1690        )
1691
1692    # Common attributes
1693
1694    def _get_inner_text(self, tag: str) -> str | None:
1695        element = self.get_element(tag)
1696        if element is None:
1697            return None
1698        return element.text
1699
1700    def _set_inner_text(self, tag: str, text: str) -> None:
1701        element = self.get_element(tag)
1702        if element is None:
1703            element = Element.from_tag(tag)
1704            self.append(element)
1705        element.text = text
1706
1707    # Dublin core
1708
1709    @property
1710    def dc_creator(self) -> str | None:
1711        """Get dc:creator value.
1712
1713        Return: str (or None if inexistant)
1714        """
1715        return self._get_inner_text("dc:creator")
1716
1717    @dc_creator.setter
1718    def dc_creator(self, creator: str) -> None:
1719        """Set dc:creator value.
1720
1721        Arguments:
1722
1723            creator -- str
1724        """
1725        self._set_inner_text("dc:creator", creator)
1726
1727    @property
1728    def dc_date(self) -> datetime | None:
1729        """Get the dc:date value.
1730
1731        Return: datetime (or None if inexistant)
1732        """
1733        date = self._get_inner_text("dc:date")
1734        if date is None:
1735            return None
1736        return DateTime.decode(date)
1737
1738    @dc_date.setter
1739    def dc_date(self, date: datetime) -> None:
1740        """Set the dc:date value.
1741
1742        Arguments:
1743
1744            darz -- datetime
1745        """
1746        self._set_inner_text("dc:date", DateTime.encode(date))
1747
1748    # SVG
1749
1750    @property
1751    def svg_title(self) -> str | None:
1752        return self._get_inner_text("svg:title")
1753
1754    @svg_title.setter
1755    def svg_title(self, title: str) -> None:
1756        self._set_inner_text("svg:title", title)
1757
1758    @property
1759    def svg_description(self) -> str | None:
1760        return self._get_inner_text("svg:desc")
1761
1762    @svg_description.setter
1763    def svg_description(self, description: str) -> None:
1764        self._set_inner_text("svg:desc", description)
1765
1766    # Sections
1767
1768    def get_sections(
1769        self,
1770        style: str | None = None,
1771        content: str | None = None,
1772    ) -> list[Element]:
1773        """Return all the sections that match the criteria.
1774
1775        Arguments:
1776
1777            style -- str
1778
1779            content -- str regex
1780
1781        Return: list of Element
1782        """
1783        return self._filtered_elements(
1784            "text:section", text_style=style, content=content
1785        )
1786
1787    def get_section(
1788        self,
1789        position: int = 0,
1790        content: str | None = None,
1791    ) -> Element | None:
1792        """Return the section that matches the criteria.
1793
1794        Arguments:
1795
1796            position -- int
1797
1798            content -- str regex
1799
1800        Return: Element or None if not found
1801        """
1802        return self._filtered_element(
1803            "descendant::text:section", position, content=content
1804        )
1805
1806    # Paragraphs
1807
1808    def get_paragraphs(
1809        self,
1810        style: str | None = None,
1811        content: str | None = None,
1812    ) -> list[Element]:
1813        """Return all the paragraphs that match the criteria.
1814
1815        Arguments:
1816
1817            style -- str
1818
1819            content -- str regex
1820
1821        Return: list of Paragraph
1822        """
1823        return self._filtered_elements(
1824            "descendant::text:p", text_style=style, content=content
1825        )
1826
1827    def get_paragraph(
1828        self,
1829        position: int = 0,
1830        content: str | None = None,
1831    ) -> Element | None:
1832        """Return the paragraph that matches the criteria.
1833
1834        Arguments:
1835
1836            position -- int
1837
1838            content -- str regex
1839
1840        Return: Paragraph or None if not found
1841        """
1842        return self._filtered_element("descendant::text:p", position, content=content)
1843
1844    # Span
1845
1846    def get_spans(
1847        self,
1848        style: str | None = None,
1849        content: str | None = None,
1850    ) -> list[Element]:
1851        """Return all the spans that match the criteria.
1852
1853        Arguments:
1854
1855            style -- str
1856
1857            content -- str regex
1858
1859        Return: list of Span
1860        """
1861        return self._filtered_elements(
1862            "descendant::text:span", text_style=style, content=content
1863        )
1864
1865    def get_span(
1866        self,
1867        position: int = 0,
1868        content: str | None = None,
1869    ) -> Element | None:
1870        """Return the span that matches the criteria.
1871
1872        Arguments:
1873
1874            position -- int
1875
1876            content -- str regex
1877
1878        Return: Span or None if not found
1879        """
1880        return self._filtered_element(
1881            "descendant::text:span", position, content=content
1882        )
1883
1884    # Headers
1885
1886    def get_headers(
1887        self,
1888        style: str | None = None,
1889        outline_level: str | None = None,
1890        content: str | None = None,
1891    ) -> list[Element]:
1892        """Return all the Headers that match the criteria.
1893
1894        Arguments:
1895
1896            style -- str
1897
1898            content -- str regex
1899
1900        Return: list of Header
1901        """
1902        return self._filtered_elements(
1903            "descendant::text:h",
1904            text_style=style,
1905            outline_level=outline_level,
1906            content=content,
1907        )
1908
1909    def get_header(
1910        self,
1911        position: int = 0,
1912        outline_level: str | None = None,
1913        content: str | None = None,
1914    ) -> Element | None:
1915        """Return the Header that matches the criteria.
1916
1917        Arguments:
1918
1919            position -- int
1920
1921            content -- str regex
1922
1923        Return: Header or None if not found
1924        """
1925        return self._filtered_element(
1926            "descendant::text:h",
1927            position,
1928            outline_level=outline_level,
1929            content=content,
1930        )
1931
1932    # Lists
1933
1934    def get_lists(
1935        self,
1936        style: str | None = None,
1937        content: str | None = None,
1938    ) -> list[Element]:
1939        """Return all the lists that match the criteria.
1940
1941        Arguments:
1942
1943            style -- str
1944
1945            content -- str regex
1946
1947        Return: list of List
1948        """
1949        return self._filtered_elements(
1950            "descendant::text:list", text_style=style, content=content
1951        )
1952
1953    def get_list(
1954        self,
1955        position: int = 0,
1956        content: str | None = None,
1957    ) -> Element | None:
1958        """Return the list that matches the criteria.
1959
1960        Arguments:
1961
1962            position -- int
1963
1964            content -- str regex
1965
1966        Return: List or None if not found
1967        """
1968        return self._filtered_element(
1969            "descendant::text:list", position, content=content
1970        )
1971
1972    # Frames
1973
1974    def get_frames(
1975        self,
1976        presentation_class: str | None = None,
1977        style: str | None = None,
1978        title: str | None = None,
1979        description: str | None = None,
1980        content: str | None = None,
1981    ) -> list[Element]:
1982        """Return all the frames that match the criteria.
1983
1984        Arguments:
1985
1986            presentation_class -- str
1987
1988            style -- str
1989
1990            title -- str regex
1991
1992            description -- str regex
1993
1994            content -- str regex
1995
1996        Return: list of Frame
1997        """
1998        return self._filtered_elements(
1999            "descendant::draw:frame",
2000            presentation_class=presentation_class,
2001            draw_style=style,
2002            svg_title=title,
2003            svg_desc=description,
2004            content=content,
2005        )
2006
2007    def get_frame(
2008        self,
2009        position: int = 0,
2010        name: str | None = None,
2011        presentation_class: str | None = None,
2012        title: str | None = None,
2013        description: str | None = None,
2014        content: str | None = None,
2015    ) -> Element | None:
2016        """Return the section that matches the criteria.
2017
2018        Arguments:
2019
2020            position -- int
2021
2022            name -- str
2023
2024            presentation_class -- str
2025
2026            title -- str regex
2027
2028            description -- str regex
2029
2030            content -- str regex
2031
2032        Return: Frame or None if not found
2033        """
2034        return self._filtered_element(
2035            "descendant::draw:frame",
2036            position,
2037            draw_name=name,
2038            presentation_class=presentation_class,
2039            svg_title=title,
2040            svg_desc=description,
2041            content=content,
2042        )
2043
2044    # Images
2045
2046    def get_images(
2047        self,
2048        style: str | None = None,
2049        url: str | None = None,
2050        content: str | None = None,
2051    ) -> list[Element]:
2052        """Return all the images matching the criteria.
2053
2054        Arguments:
2055
2056            style -- str
2057
2058            url -- str regex
2059
2060            content -- str regex
2061
2062        Return: list of Element
2063        """
2064        return self._filtered_elements(
2065            "descendant::draw:image", text_style=style, url=url, content=content
2066        )
2067
2068    def get_image(
2069        self,
2070        position: int = 0,
2071        name: str | None = None,
2072        url: str | None = None,
2073        content: str | None = None,
2074    ) -> Element | None:
2075        """Return the image matching the criteria.
2076
2077        Arguments:
2078
2079            position -- int
2080
2081            name -- str
2082
2083            url -- str regex
2084
2085            content -- str regex
2086
2087        Return: Element or None if not found
2088        """
2089        # The frame is holding the name
2090        if name is not None:
2091            frame = self._filtered_element(
2092                "descendant::draw:frame", position, draw_name=name
2093            )
2094            if frame is None:
2095                return None
2096            # The name is supposedly unique
2097            return frame.get_element("draw:image")
2098        return self._filtered_element(
2099            "descendant::draw:image", position, url=url, content=content
2100        )
2101
2102    # Tables
2103
2104    def get_tables(
2105        self,
2106        style: str | None = None,
2107        content: str | None = None,
2108    ) -> list[Element]:
2109        """Return all the tables that match the criteria.
2110
2111        Arguments:
2112
2113            style -- str
2114
2115            content -- str regex
2116
2117        Return: list of Table
2118        """
2119        return self._filtered_elements(
2120            "descendant::table:table", table_style=style, content=content
2121        )
2122
2123    def get_table(
2124        self,
2125        position: int = 0,
2126        name: str | None = None,
2127        content: str | None = None,
2128    ) -> Element | None:
2129        """Return the table that matches the criteria.
2130
2131        Arguments:
2132
2133            position -- int
2134
2135            name -- str
2136
2137            content -- str regex
2138
2139        Return: Table or None if not found
2140        """
2141        if name is None and content is None:
2142            result = self._filtered_element("descendant::table:table", position)
2143        else:
2144            result = self._filtered_element(
2145                "descendant::table:table",
2146                position,
2147                table_name=name,
2148                content=content,
2149            )
2150        return result
2151
2152    # Named Range
2153
2154    def get_named_ranges(self) -> list[Element]:
2155        """Return all the tables named ranges.
2156
2157        Return: list of odf_named_range
2158        """
2159        named_ranges = self.get_elements(
2160            "descendant::table:named-expressions/table:named-range"
2161        )
2162        return named_ranges
2163
2164    def get_named_range(self, name: str) -> Element | None:
2165        """Return the named range of specified name, or None if not found.
2166
2167        Arguments:
2168
2169            name -- str
2170
2171        Return: NamedRange
2172        """
2173        named_range = self.get_elements(
2174            f'descendant::table:named-expressions/table:named-range[@table:name="{name}"][1]'
2175        )
2176        if named_range:
2177            return named_range[0]
2178        else:
2179            return None
2180
2181    def append_named_range(self, named_range: Element) -> None:
2182        """Append the named range to the spreadsheet, replacing existing named
2183        range of same name if any.
2184
2185        Arguments:
2186
2187            named_range --  NamedRange
2188        """
2189        if self.tag != "office:spreadsheet":
2190            raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}")
2191        named_expressions = self.get_element("table:named-expressions")
2192        if not named_expressions:
2193            named_expressions = Element.from_tag("table:named-expressions")
2194            self.append(named_expressions)
2195        # exists ?
2196        current = named_expressions.get_element(
2197            f'table:named-range[@table:name="{named_range.name}"][1]'  # type:ignore
2198        )
2199        if current:
2200            named_expressions.delete(current)
2201        named_expressions.append(named_range)
2202
2203    def delete_named_range(self, name: str) -> None:
2204        """Delete the Named Range of specified name from the spreadsheet.
2205
2206        Arguments:
2207
2208            name -- str
2209        """
2210        if self.tag != "office:spreadsheet":
2211            raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}")
2212        named_range = self.get_named_range(name)
2213        if not named_range:
2214            return
2215        named_range.delete()
2216        named_expressions = self.get_element("table:named-expressions")
2217        if not named_expressions:
2218            return
2219        element = named_expressions.__element
2220        children = list(element.iterchildren())
2221        if not children:
2222            self.delete(named_expressions)
2223
2224    # Notes
2225
2226    def get_notes(
2227        self,
2228        note_class: str | None = None,
2229        content: str | None = None,
2230    ) -> list[Element]:
2231        """Return all the notes that match the criteria.
2232
2233        Arguments:
2234
2235            note_class -- 'footnote' or 'endnote'
2236
2237            content -- str regex
2238
2239        Return: list of Note
2240        """
2241        return self._filtered_elements(
2242            "descendant::text:note", note_class=note_class, content=content
2243        )
2244
2245    def get_note(
2246        self,
2247        position: int = 0,
2248        note_id: str | None = None,
2249        note_class: str | None = None,
2250        content: str | None = None,
2251    ) -> Element | None:
2252        """Return the note that matches the criteria.
2253
2254        Arguments:
2255
2256            position -- int
2257
2258            note_id -- str
2259
2260            note_class -- 'footnote' or 'endnote'
2261
2262            content -- str regex
2263
2264        Return: Note or None if not found
2265        """
2266        return self._filtered_element(
2267            "descendant::text:note",
2268            position,
2269            text_id=note_id,
2270            note_class=note_class,
2271            content=content,
2272        )
2273
2274    # Annotations
2275
2276    def get_annotations(
2277        self,
2278        creator: str | None = None,
2279        start_date: datetime | None = None,
2280        end_date: datetime | None = None,
2281        content: str | None = None,
2282    ) -> list[Element]:
2283        """Return all the annotations that match the criteria.
2284
2285        Arguments:
2286
2287            creator -- str
2288
2289            start_date -- datetime instance
2290
2291            end_date --  datetime instance
2292
2293            content -- str regex
2294
2295        Return: list of Annotation
2296        """
2297        annotations = []
2298        for annotation in self._filtered_elements(
2299            "descendant::office:annotation", content=content
2300        ):
2301            if creator is not None and creator != annotation.dc_creator:
2302                continue
2303            date = annotation.dc_date
2304            if date is None:
2305                continue
2306            if start_date is not None and date < start_date:
2307                continue
2308            if end_date is not None and date >= end_date:
2309                continue
2310            annotations.append(annotation)
2311        return annotations
2312
2313    def get_annotation(
2314        self,
2315        position: int = 0,
2316        creator: str | None = None,
2317        start_date: datetime | None = None,
2318        end_date: datetime | None = None,
2319        content: str | None = None,
2320        name: str | None = None,
2321    ) -> Element | None:
2322        """Return the annotation that matches the criteria.
2323
2324        Arguments:
2325
2326            position -- int
2327
2328            creator -- str
2329
2330            start_date -- datetime instance
2331
2332            end_date -- datetime instance
2333
2334            content -- str regex
2335
2336            name -- str
2337
2338        Return: Annotation or None if not found
2339        """
2340        if name is not None:
2341            return self._filtered_element(
2342                "descendant::office:annotation", 0, office_name=name
2343            )
2344        annotations = self.get_annotations(
2345            creator=creator, start_date=start_date, end_date=end_date, content=content
2346        )
2347        if not annotations:
2348            return None
2349        try:
2350            return annotations[position]
2351        except IndexError:
2352            return None
2353
2354    def get_annotation_ends(self) -> list[Element]:
2355        """Return all the annotation ends.
2356
2357        Return: list of Element
2358        """
2359        return self._filtered_elements("descendant::office:annotation-end")
2360
2361    def get_annotation_end(
2362        self,
2363        position: int = 0,
2364        name: str | None = None,
2365    ) -> Element | None:
2366        """Return the annotation end that matches the criteria.
2367
2368        Arguments:
2369
2370            position -- int
2371
2372            name -- str
2373
2374        Return: Element or None if not found
2375        """
2376        return self._filtered_element(
2377            "descendant::office:annotation-end", position, office_name=name
2378        )
2379
2380    # office:names
2381
2382    def get_office_names(self) -> list[str]:
2383        """Return all the used office:name tags values of the element.
2384
2385        Return: list of unique str
2386        """
2387        name_xpath_query = xpath_compile("//@office:name")
2388        response = name_xpath_query(self.__element)
2389        if not isinstance(response, list):
2390            return []
2391        return list({str(name) for name in response if name})
2392
2393    # Variables
2394
2395    def get_variable_decls(self) -> Element:
2396        """Return the container for variable declarations. Created if not
2397        found.
2398
2399        Return: Element
2400        """
2401        variable_decls = self.get_element("//text:variable-decls")
2402        if variable_decls is None:
2403            body = self.document_body
2404            if not body:
2405                raise ValueError("Empty document.body")
2406            body.insert(Element.from_tag("text:variable-decls"), FIRST_CHILD)
2407            variable_decls = body.get_element("//text:variable-decls")
2408
2409        return variable_decls  # type:ignore
2410
2411    def get_variable_decl_list(self) -> list[Element]:
2412        """Return all the variable declarations.
2413
2414        Return: list of Element
2415        """
2416        return self._filtered_elements("descendant::text:variable-decl")
2417
2418    def get_variable_decl(self, name: str, position: int = 0) -> Element | None:
2419        """return the variable declaration for the given name.
2420
2421        Arguments:
2422
2423            name -- str
2424
2425            position -- int
2426
2427        return: Element or none if not found
2428        """
2429        return self._filtered_element(
2430            "descendant::text:variable-decl", position, text_name=name
2431        )
2432
2433    def get_variable_sets(self, name: str | None = None) -> list[Element]:
2434        """Return all the variable sets that match the criteria.
2435
2436        Arguments:
2437
2438            name -- str
2439
2440        Return: list of Element
2441        """
2442        return self._filtered_elements("descendant::text:variable-set", text_name=name)
2443
2444    def get_variable_set(self, name: str, position: int = -1) -> Element | None:
2445        """Return the variable set for the given name (last one by default).
2446
2447        Arguments:
2448
2449            name -- str
2450
2451            position -- int
2452
2453        Return: Element or None if not found
2454        """
2455        return self._filtered_element(
2456            "descendant::text:variable-set", position, text_name=name
2457        )
2458
2459    def get_variable_set_value(
2460        self,
2461        name: str,
2462        value_type: str | None = None,
2463    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2464        """Return the last value of the given variable name.
2465
2466        Arguments:
2467
2468            name -- str
2469
2470            value_type -- 'boolean', 'currency', 'date', 'float',
2471                          'percentage', 'string', 'time' or automatic
2472
2473        Return: most appropriate Python type
2474        """
2475        variable_set = self.get_variable_set(name)
2476        if not variable_set:
2477            return None
2478        return variable_set.get_value(value_type)  # type: ignore
2479
2480    # User fields
2481
2482    def get_user_field_decls(self) -> Element | None:
2483        """Return the container for user field declarations. Created if not
2484        found.
2485
2486        Return: Element
2487        """
2488        user_field_decls = self.get_element("//text:user-field-decls")
2489        if user_field_decls is None:
2490            body = self.document_body
2491            if not body:
2492                raise ValueError("Empty document.body")
2493            body.insert(Element.from_tag("text:user-field-decls"), FIRST_CHILD)
2494            user_field_decls = body.get_element("//text:user-field-decls")
2495
2496        return user_field_decls
2497
2498    def get_user_field_decl_list(self) -> list[Element]:
2499        """Return all the user field declarations.
2500
2501        Return: list of Element
2502        """
2503        return self._filtered_elements("descendant::text:user-field-decl")
2504
2505    def get_user_field_decl(self, name: str, position: int = 0) -> Element | None:
2506        """return the user field declaration for the given name.
2507
2508        return: Element or none if not found
2509        """
2510        return self._filtered_element(
2511            "descendant::text:user-field-decl", position, text_name=name
2512        )
2513
2514    def get_user_field_value(
2515        self, name: str, value_type: str | None = None
2516    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2517        """Return the value of the given user field name.
2518
2519        Arguments:
2520
2521            name -- str
2522
2523            value_type -- 'boolean', 'currency', 'date', 'float',
2524                          'percentage', 'string', 'time' or automatic
2525
2526        Return: most appropriate Python type
2527        """
2528        user_field_decl = self.get_user_field_decl(name)
2529        if user_field_decl is None:
2530            return None
2531        return user_field_decl.get_value(value_type)  # type: ignore
2532
2533    # User defined fields
2534    # They are fields who should contain a copy of a user defined medtadata
2535
2536    def get_user_defined_list(self) -> list[Element]:
2537        """Return all the user defined field declarations.
2538
2539        Return: list of Element
2540        """
2541        return self._filtered_elements("descendant::text:user-defined")
2542
2543    def get_user_defined(self, name: str, position: int = 0) -> Element | None:
2544        """return the user defined declaration for the given name.
2545
2546        return: Element or none if not found
2547        """
2548        return self._filtered_element(
2549            "descendant::text:user-defined", position, text_name=name
2550        )
2551
2552    def get_user_defined_value(
2553        self, name: str, value_type: str | None = None
2554    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2555        """Return the value of the given user defined field name.
2556
2557        Arguments:
2558
2559            name -- str
2560
2561            value_type -- 'boolean', 'date', 'float',
2562                          'string', 'time' or automatic
2563
2564        Return: most appropriate Python type
2565        """
2566        user_defined = self.get_user_defined(name)
2567        if user_defined is None:
2568            return None
2569        return user_defined.get_value(value_type)  # type: ignore
2570
2571    # Draw Pages
2572
2573    def get_draw_pages(
2574        self,
2575        style: str | None = None,
2576        content: str | None = None,
2577    ) -> list[Element]:
2578        """Return all the draw pages that match the criteria.
2579
2580        Arguments:
2581
2582            style -- str
2583
2584            content -- str regex
2585
2586        Return: list of DrawPage
2587        """
2588        return self._filtered_elements(
2589            "descendant::draw:page", draw_style=style, content=content
2590        )
2591
2592    def get_draw_page(
2593        self,
2594        position: int = 0,
2595        name: str | None = None,
2596        content: str | None = None,
2597    ) -> Element | None:
2598        """Return the draw page that matches the criteria.
2599
2600        Arguments:
2601
2602            position -- int
2603
2604            name -- str
2605
2606            content -- str regex
2607
2608        Return: DrawPage or None if not found
2609        """
2610        return self._filtered_element(
2611            "descendant::draw:page", position, draw_name=name, content=content
2612        )
2613
2614    # Links
2615
2616    def get_links(
2617        self,
2618        name: str | None = None,
2619        title: str | None = None,
2620        url: str | None = None,
2621        content: str | None = None,
2622    ) -> list[Element]:
2623        """Return all the links that match the criteria.
2624
2625        Arguments:
2626
2627            name -- str
2628
2629            title -- str
2630
2631            url -- str regex
2632
2633            content -- str regex
2634
2635        Return: list of Element
2636        """
2637        return self._filtered_elements(
2638            "descendant::text:a",
2639            office_name=name,
2640            office_title=title,
2641            url=url,
2642            content=content,
2643        )
2644
2645    def get_link(
2646        self,
2647        position: int = 0,
2648        name: str | None = None,
2649        title: str | None = None,
2650        url: str | None = None,
2651        content: str | None = None,
2652    ) -> Element | None:
2653        """Return the link that matches the criteria.
2654
2655        Arguments:
2656
2657            position -- int
2658
2659            name -- str
2660
2661            title -- str
2662
2663            url -- str regex
2664
2665            content -- str regex
2666
2667        Return: Element or None if not found
2668        """
2669        return self._filtered_element(
2670            "descendant::text:a",
2671            position,
2672            office_name=name,
2673            office_title=title,
2674            url=url,
2675            content=content,
2676        )
2677
2678    # Bookmarks
2679
2680    def get_bookmarks(self) -> list[Element]:
2681        """Return all the bookmarks.
2682
2683        Return: list of Element
2684        """
2685        return self._filtered_elements("descendant::text:bookmark")
2686
2687    def get_bookmark(
2688        self,
2689        position: int = 0,
2690        name: str | None = None,
2691    ) -> Element | None:
2692        """Return the bookmark that matches the criteria.
2693
2694        Arguments:
2695
2696            position -- int
2697
2698            name -- str
2699
2700        Return: Bookmark or None if not found
2701        """
2702        return self._filtered_element(
2703            "descendant::text:bookmark", position, text_name=name
2704        )
2705
2706    def get_bookmark_starts(self) -> list[Element]:
2707        """Return all the bookmark starts.
2708
2709        Return: list of Element
2710        """
2711        return self._filtered_elements("descendant::text:bookmark-start")
2712
2713    def get_bookmark_start(
2714        self,
2715        position: int = 0,
2716        name: str | None = None,
2717    ) -> Element | None:
2718        """Return the bookmark start that matches the criteria.
2719
2720        Arguments:
2721
2722            position -- int
2723
2724            name -- str
2725
2726        Return: Element or None if not found
2727        """
2728        return self._filtered_element(
2729            "descendant::text:bookmark-start", position, text_name=name
2730        )
2731
2732    def get_bookmark_ends(self) -> list[Element]:
2733        """Return all the bookmark ends.
2734
2735        Return: list of Element
2736        """
2737        return self._filtered_elements("descendant::text:bookmark-end")
2738
2739    def get_bookmark_end(
2740        self,
2741        position: int = 0,
2742        name: str | None = None,
2743    ) -> Element | None:
2744        """Return the bookmark end that matches the criteria.
2745
2746        Arguments:
2747
2748            position -- int
2749
2750            name -- str
2751
2752        Return: Element or None if not found
2753        """
2754        return self._filtered_element(
2755            "descendant::text:bookmark-end", position, text_name=name
2756        )
2757
2758    # Reference marks
2759
2760    def get_reference_marks_single(self) -> list[Element]:
2761        """Return all the reference marks. Search only the tags
2762        text:reference-mark.
2763        Consider using : get_reference_marks()
2764
2765        Return: list of Element
2766        """
2767        return self._filtered_elements("descendant::text:reference-mark")
2768
2769    def get_reference_mark_single(
2770        self,
2771        position: int = 0,
2772        name: str | None = None,
2773    ) -> Element | None:
2774        """Return the reference mark that matches the criteria. Search only the
2775        tags text:reference-mark.
2776        Consider using : get_reference_mark()
2777
2778        Arguments:
2779
2780            position -- int
2781
2782            name -- str
2783
2784        Return: Element or None if not found
2785        """
2786        return self._filtered_element(
2787            "descendant::text:reference-mark", position, text_name=name
2788        )
2789
2790    def get_reference_mark_starts(self) -> list[Element]:
2791        """Return all the reference mark starts. Search only the tags
2792        text:reference-mark-start.
2793        Consider using : get_reference_marks()
2794
2795        Return: list of Element
2796        """
2797        return self._filtered_elements("descendant::text:reference-mark-start")
2798
2799    def get_reference_mark_start(
2800        self,
2801        position: int = 0,
2802        name: str | None = None,
2803    ) -> Element | None:
2804        """Return the reference mark start that matches the criteria. Search
2805        only the tags text:reference-mark-start.
2806        Consider using : get_reference_mark()
2807
2808        Arguments:
2809
2810            position -- int
2811
2812            name -- str
2813
2814        Return: Element or None if not found
2815        """
2816        return self._filtered_element(
2817            "descendant::text:reference-mark-start", position, text_name=name
2818        )
2819
2820    def get_reference_mark_ends(self) -> list[Element]:
2821        """Return all the reference mark ends. Search only the tags
2822        text:reference-mark-end.
2823        Consider using : get_reference_marks()
2824
2825        Return: list of Element
2826        """
2827        return self._filtered_elements("descendant::text:reference-mark-end")
2828
2829    def get_reference_mark_end(
2830        self,
2831        position: int = 0,
2832        name: str | None = None,
2833    ) -> Element | None:
2834        """Return the reference mark end that matches the criteria. Search only
2835        the tags text:reference-mark-end.
2836        Consider using : get_reference_marks()
2837
2838        Arguments:
2839
2840            position -- int
2841
2842            name -- str
2843
2844        Return: Element or None if not found
2845        """
2846        return self._filtered_element(
2847            "descendant::text:reference-mark-end", position, text_name=name
2848        )
2849
2850    def get_reference_marks(self) -> list[Element]:
2851        """Return all the reference marks, either single position reference
2852        (text:reference-mark) or start of range reference
2853        (text:reference-mark-start).
2854
2855        Return: list of Element
2856        """
2857        return self._filtered_elements(
2858            "descendant::text:reference-mark-start | descendant::text:reference-mark"
2859        )
2860
2861    def get_reference_mark(
2862        self,
2863        position: int = 0,
2864        name: str | None = None,
2865    ) -> Element | None:
2866        """Return the reference mark that match the criteria. Either single
2867        position reference mark (text:reference-mark) or start of range
2868        reference (text:reference-mark-start).
2869
2870        Arguments:
2871
2872            position -- int
2873
2874            name -- str
2875
2876        Return: Element or None if not found
2877        """
2878        if name:
2879            request = (
2880                f"descendant::text:reference-mark-start"
2881                f'[@text:name="{name}"] '
2882                f"| descendant::text:reference-mark"
2883                f'[@text:name="{name}"]'
2884            )
2885            return self._filtered_element(request, position=0)
2886        request = (
2887            "descendant::text:reference-mark-start | descendant::text:reference-mark"
2888        )
2889        return self._filtered_element(request, position)
2890
2891    def get_references(self, name: str | None = None) -> list[Element]:
2892        """Return all the references (text:reference-ref). If name is
2893        provided, returns the references of that name.
2894
2895        Return: list of Element
2896
2897        Arguments:
2898
2899            name -- str or None
2900        """
2901        if name is None:
2902            return self._filtered_elements("descendant::text:reference-ref")
2903        request = f'descendant::text:reference-ref[@text:ref-name="{name}"]'
2904        return self._filtered_elements(request)
2905
2906    # Shapes elements
2907
2908    # Groups
2909
2910    def get_draw_groups(
2911        self,
2912        title: str | None = None,
2913        description: str | None = None,
2914        content: str | None = None,
2915    ) -> list[Element]:
2916        return self._filtered_elements(
2917            "descendant::draw:g",
2918            svg_title=title,
2919            svg_desc=description,
2920            content=content,
2921        )
2922
2923    def get_draw_group(
2924        self,
2925        position: int = 0,
2926        name: str | None = None,
2927        title: str | None = None,
2928        description: str | None = None,
2929        content: str | None = None,
2930    ) -> Element | None:
2931        return self._filtered_element(
2932            "descendant::draw:g",
2933            position,
2934            draw_name=name,
2935            svg_title=title,
2936            svg_desc=description,
2937            content=content,
2938        )
2939
2940    # Lines
2941
2942    def get_draw_lines(
2943        self,
2944        draw_style: str | None = None,
2945        draw_text_style: str | None = None,
2946        content: str | None = None,
2947    ) -> list[Element]:
2948        """Return all the draw lines that match the criteria.
2949
2950        Arguments:
2951
2952            draw_style -- str
2953
2954            draw_text_style -- str
2955
2956            content -- str regex
2957
2958        Return: list of odf_shape
2959        """
2960        return self._filtered_elements(
2961            "descendant::draw:line",
2962            draw_style=draw_style,
2963            draw_text_style=draw_text_style,
2964            content=content,
2965        )
2966
2967    def get_draw_line(
2968        self,
2969        position: int = 0,
2970        id: str | None = None,  # noqa:A002
2971        content: str | None = None,
2972    ) -> Element | None:
2973        """Return the draw line that matches the criteria.
2974
2975        Arguments:
2976
2977            position -- int
2978
2979            id -- str
2980
2981            content -- str regex
2982
2983        Return: odf_shape or None if not found
2984        """
2985        return self._filtered_element(
2986            "descendant::draw:line", position, draw_id=id, content=content
2987        )
2988
2989    # Rectangles
2990
2991    def get_draw_rectangles(
2992        self,
2993        draw_style: str | None = None,
2994        draw_text_style: str | None = None,
2995        content: str | None = None,
2996    ) -> list[Element]:
2997        """Return all the draw rectangles that match the criteria.
2998
2999        Arguments:
3000
3001            draw_style -- str
3002
3003            draw_text_style -- str
3004
3005            content -- str regex
3006
3007        Return: list of odf_shape
3008        """
3009        return self._filtered_elements(
3010            "descendant::draw:rect",
3011            draw_style=draw_style,
3012            draw_text_style=draw_text_style,
3013            content=content,
3014        )
3015
3016    def get_draw_rectangle(
3017        self,
3018        position: int = 0,
3019        id: str | None = None,  # noqa:A002
3020        content: str | None = None,
3021    ) -> Element | None:
3022        """Return the draw rectangle that matches the criteria.
3023
3024        Arguments:
3025
3026            position -- int
3027
3028            id -- str
3029
3030            content -- str regex
3031
3032        Return: odf_shape or None if not found
3033        """
3034        return self._filtered_element(
3035            "descendant::draw:rect", position, draw_id=id, content=content
3036        )
3037
3038    # Ellipse
3039
3040    def get_draw_ellipses(
3041        self,
3042        draw_style: str | None = None,
3043        draw_text_style: str | None = None,
3044        content: str | None = None,
3045    ) -> list[Element]:
3046        """Return all the draw ellipses that match the criteria.
3047
3048        Arguments:
3049
3050            draw_style -- str
3051
3052            draw_text_style -- str
3053
3054            content -- str regex
3055
3056        Return: list of odf_shape
3057        """
3058        return self._filtered_elements(
3059            "descendant::draw:ellipse",
3060            draw_style=draw_style,
3061            draw_text_style=draw_text_style,
3062            content=content,
3063        )
3064
3065    def get_draw_ellipse(
3066        self,
3067        position: int = 0,
3068        id: str | None = None,  # noqa:A002
3069        content: str | None = None,
3070    ) -> Element | None:
3071        """Return the draw ellipse that matches the criteria.
3072
3073        Arguments:
3074
3075            position -- int
3076
3077            id -- str
3078
3079            content -- str regex
3080
3081        Return: odf_shape or None if not found
3082        """
3083        return self._filtered_element(
3084            "descendant::draw:ellipse", position, draw_id=id, content=content
3085        )
3086
3087    # Connectors
3088
3089    def get_draw_connectors(
3090        self,
3091        draw_style: str | None = None,
3092        draw_text_style: str | None = None,
3093        content: str | None = None,
3094    ) -> list[Element]:
3095        """Return all the draw connectors that match the criteria.
3096
3097        Arguments:
3098
3099            draw_style -- str
3100
3101            draw_text_style -- str
3102
3103            content -- str regex
3104
3105        Return: list of odf_shape
3106        """
3107        return self._filtered_elements(
3108            "descendant::draw:connector",
3109            draw_style=draw_style,
3110            draw_text_style=draw_text_style,
3111            content=content,
3112        )
3113
3114    def get_draw_connector(
3115        self,
3116        position: int = 0,
3117        id: str | None = None,  # noqa:A002
3118        content: str | None = None,
3119    ) -> Element | None:
3120        """Return the draw connector that matches the criteria.
3121
3122        Arguments:
3123
3124            position -- int
3125
3126            id -- str
3127
3128            content -- str regex
3129
3130        Return: odf_shape or None if not found
3131        """
3132        return self._filtered_element(
3133            "descendant::draw:connector", position, draw_id=id, content=content
3134        )
3135
3136    def get_orphan_draw_connectors(self) -> list[Element]:
3137        """Return a list of connectors which don't have any shape connected
3138        to them.
3139        """
3140        connectors = []
3141        for connector in self.get_draw_connectors():
3142            start_shape = connector.get_attribute("draw:start-shape")
3143            end_shape = connector.get_attribute("draw:end-shape")
3144            if start_shape is None and end_shape is None:
3145                connectors.append(connector)
3146        return connectors
3147
3148    # Tracked changes and text change
3149
3150    def get_tracked_changes(self) -> Element | None:
3151        """Return the tracked-changes part in the text body."""
3152        return self.get_element("//text:tracked-changes")
3153
3154    def get_changes_ids(self) -> list[Element | Text]:
3155        """Return a list of ids that refers to a change region in the tracked
3156        changes list.
3157        """
3158        # Insertion changes
3159        xpath_query = "descendant::text:change-start/@text:change-id"
3160        # Deletion changes
3161        xpath_query += " | descendant::text:change/@text:change-id"
3162        return self.xpath(xpath_query)
3163
3164    def get_text_change_deletions(self) -> list[Element]:
3165        """Return all the text changes of deletion kind: the tags text:change.
3166        Consider using : get_text_changes()
3167
3168        Return: list of Element
3169        """
3170        return self._filtered_elements("descendant::text:text:change")
3171
3172    def get_text_change_deletion(
3173        self,
3174        position: int = 0,
3175        idx: str | None = None,
3176    ) -> Element | None:
3177        """Return the text change of deletion kind that matches the criteria.
3178        Search only for the tags text:change.
3179        Consider using : get_text_change()
3180
3181        Arguments:
3182
3183            position -- int
3184
3185            idx -- str
3186
3187        Return: Element or None if not found
3188        """
3189        return self._filtered_element(
3190            "descendant::text:change", position, change_id=idx
3191        )
3192
3193    def get_text_change_starts(self) -> list[Element]:
3194        """Return all the text change-start. Search only for the tags
3195        text:change-start.
3196        Consider using : get_text_changes()
3197
3198        Return: list of Element
3199        """
3200        return self._filtered_elements("descendant::text:change-start")
3201
3202    def get_text_change_start(
3203        self,
3204        position: int = 0,
3205        idx: str | None = None,
3206    ) -> Element | None:
3207        """Return the text change-start that matches the criteria. Search
3208        only the tags text:change-start.
3209        Consider using : get_text_change()
3210
3211        Arguments:
3212
3213            position -- int
3214
3215            idx -- str
3216
3217        Return: Element or None if not found
3218        """
3219        return self._filtered_element(
3220            "descendant::text:change-start", position, change_id=idx
3221        )
3222
3223    def get_text_change_ends(self) -> list[Element]:
3224        """Return all the text change-end. Search only the tags
3225        text:change-end.
3226        Consider using : get_text_changes()
3227
3228        Return: list of Element
3229        """
3230        return self._filtered_elements("descendant::text:change-end")
3231
3232    def get_text_change_end(
3233        self,
3234        position: int = 0,
3235        idx: str | None = None,
3236    ) -> Element | None:
3237        """Return the text change-end that matches the criteria. Search only
3238        the tags text:change-end.
3239        Consider using : get_text_change()
3240
3241        Arguments:
3242
3243            position -- int
3244
3245            idx -- str
3246
3247        Return: Element or None if not found
3248        """
3249        return self._filtered_element(
3250            "descendant::text:change-end", position, change_id=idx
3251        )
3252
3253    def get_text_changes(self) -> list[Element]:
3254        """Return all the text changes, either single deletion
3255        (text:change) or start of range of changes (text:change-start).
3256
3257        Return: list of Element
3258        """
3259        request = "descendant::text:change-start | descendant::text:change"
3260        return self._filtered_elements(request)
3261
3262    def get_text_change(
3263        self,
3264        position: int = 0,
3265        idx: str | None = None,
3266    ) -> Element | None:
3267        """Return the text change that matches the criteria. Either single
3268        deletion (text:change) or start of range of changes (text:change-start).
3269        position : index of the element to retrieve if several matches, default
3270        is 0.
3271        idx : change-id of the element.
3272
3273        Arguments:
3274
3275            position -- int
3276
3277            idx -- str
3278
3279        Return: Element or None if not found
3280        """
3281        if idx:
3282            request = (
3283                f'descendant::text:change-start[@text:change-id="{idx}"] '
3284                f'| descendant::text:change[@text:change-id="{idx}"]'
3285            )
3286            return self._filtered_element(request, 0)
3287        request = "descendant::text:change-start | descendant::text:change"
3288        return self._filtered_element(request, position)
3289
3290    # Table Of Content
3291
3292    def get_tocs(self) -> list[Element]:
3293        """Return all the tables of contents.
3294
3295        Return: list of odf_toc
3296        """
3297        return self._filtered_elements("text:table-of-content")
3298
3299    def get_toc(
3300        self,
3301        position: int = 0,
3302        content: str | None = None,
3303    ) -> Element | None:
3304        """Return the table of contents that matches the criteria.
3305
3306        Arguments:
3307
3308            position -- int
3309
3310            content -- str regex
3311
3312        Return: odf_toc or None if not found
3313        """
3314        return self._filtered_element(
3315            "text:table-of-content", position, content=content
3316        )
3317
3318    # Styles
3319
3320    @staticmethod
3321    def _get_style_tagname(family: str | None, is_default: bool = False) -> str:
3322        """Widely match possible tag names given the family (or not)."""
3323        if not family:
3324            tagname = "(style:default-style|*[@style:name]|draw:fill-image|draw:marker)"
3325        elif is_default:
3326            # Default style
3327            tagname = "style:default-style"
3328        else:
3329            tagname = _family_style_tagname(family)
3330            # if famattr:
3331            #    # Include family default style
3332            #    tagname = '(%s|style:default-style)' % tagname
3333            if family in FAMILY_ODF_STD:
3334                # Include family default style
3335                tagname = f"({tagname}|style:default-style)"
3336        return tagname
3337
3338    def get_styles(self, family: str | None = None) -> list[Element]:
3339        # Both common and default styles
3340        tagname = self._get_style_tagname(family)
3341        return self._filtered_elements(tagname, family=family)
3342
3343    def get_style(
3344        self,
3345        family: str,
3346        name_or_element: str | Element | None = None,
3347        display_name: str | None = None,
3348    ) -> Element | None:
3349        """Return the style uniquely identified by the family/name pair. If
3350        the argument is already a style object, it will return it.
3351
3352        If the name is not the internal name but the name you gave in the
3353        desktop application, use display_name instead.
3354
3355        Arguments:
3356
3357            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
3358                      'number'
3359
3360            name_or_element -- str or Style
3361
3362            display_name -- str
3363
3364        Return: odf_style or None if not found
3365        """
3366        if isinstance(name_or_element, Element):
3367            name = self.get_attribute("style:name")
3368            if name is not None:
3369                return name_or_element
3370            else:
3371                raise ValueError(f"Not a odf_style ? {name_or_element!r}")
3372        style_name = name_or_element
3373        is_default = not (style_name or display_name)
3374        tagname = self._get_style_tagname(family, is_default=is_default)
3375        # famattr became None if no "style:family" attribute
3376        if family:
3377            return self._filtered_element(
3378                tagname,
3379                0,
3380                style_name=style_name,
3381                display_name=display_name,
3382                family=family,
3383            )
3384        else:
3385            return self._filtered_element(
3386                tagname,
3387                0,
3388                draw_name=style_name or display_name,
3389                family=family,
3390            )
3391
3392    def _filtered_element(
3393        self,
3394        query_string: str,
3395        position: int,
3396        **kwargs: Any,
3397    ) -> Element | None:
3398        results = self._filtered_elements(query_string, **kwargs)
3399        try:
3400            return results[position]
3401        except IndexError:
3402            return None
3403
3404    def _filtered_elements(
3405        self,
3406        query_string: str,
3407        content: str | None = None,
3408        url: str | None = None,
3409        svg_title: str | None = None,
3410        svg_desc: str | None = None,
3411        dc_creator: str | None = None,
3412        dc_date: datetime | None = None,
3413        **kwargs: Any,
3414    ) -> list[Element]:
3415        query = make_xpath_query(query_string, **kwargs)
3416        elements = self.get_elements(query)
3417        # Filter the elements with the regex (TODO use XPath)
3418        if content is not None:
3419            elements = [element for element in elements if element.match(content)]
3420        if url is not None:
3421            filtered = []
3422            for element in elements:
3423                url_attr = element.get_attribute("xlink:href")
3424                if isinstance(url_attr, str) and search(url, url_attr) is not None:
3425                    filtered.append(element)
3426            elements = filtered
3427        if dc_date is None:
3428            dt_dc_date = None
3429        else:
3430            dt_dc_date = DateTime.encode(dc_date)
3431        for variable, childname in [
3432            (svg_title, "svg:title"),
3433            (svg_desc, "svg:desc"),
3434            (dc_creator, "descendant::dc:creator"),
3435            (dt_dc_date, "descendant::dc:date"),
3436        ]:
3437            if not variable:
3438                continue
3439            filtered = []
3440            for element in elements:
3441                child = element.get_element(childname)
3442                if child and child.match(variable):
3443                    filtered.append(element)
3444            elements = filtered
3445        return elements

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Element(**kwargs: Any)
316    def __init__(self, **kwargs: Any) -> None:
317        tag_or_elem = kwargs.pop("tag_or_elem", None)
318        if tag_or_elem is None:
319            # Instance for newly created object: create new lxml element and
320            # continue by subclass __init__
321            # If the tag key word exists, make a custom element
322            self._do_init = True
323            tag = kwargs.pop("tag", self._tag)
324            self.__element = self.make_etree_element(tag)
325        else:
326            # called with an existing lxml element, sould be a result of
327            # from_tag() casting, do not execute the subclass __init__
328            if not isinstance(tag_or_elem, _Element):
329                raise TypeError(f'"{type(tag_or_elem)}" is not an element node')
330            self._do_init = False
331            self.__element = tag_or_elem
@classmethod
def from_tag(cls, tag_or_elem: str | lxml.etree._Element) -> Element:
339    @classmethod
340    def from_tag(cls, tag_or_elem: str | _Element) -> Element:
341        """Element class and subclass factory.
342
343        Turn an lxml Element or ODF string tag into an ODF XML Element
344        of the relevant class.
345
346        Arguments:
347
348            tag_or_elem -- ODF str tag or lxml.Element
349
350        Return: Element (or subclass) instance
351        """
352        if isinstance(tag_or_elem, str):
353            # assume the argument is a prefix:name tag
354            elem = cls.make_etree_element(tag_or_elem)
355        else:
356            elem = tag_or_elem
357        klass = _class_registry.get(elem.tag, cls)
358        return klass(tag_or_elem=elem)

Element class and subclass factory.

Turn an lxml Element or ODF string tag into an ODF XML Element of the relevant class.

Arguments:

tag_or_elem -- ODF str tag or lxml.Element

Return: Element (or subclass) instance

@classmethod
def from_tag_for_clone( cls: type, tree_element: lxml.etree._Element, cache: tuple | None) -> Element:
360    @classmethod
361    def from_tag_for_clone(
362        cls: type,
363        tree_element: _Element,
364        cache: tuple | None,
365    ) -> Element:
366        tag = to_str(tree_element.tag)
367        klass = _class_registry.get(tag, cls)
368        element = klass(tag_or_elem=tree_element)
369        if cache and element._caching:
370            element._tmap = cache[0]
371            element._cmap = cache[1]
372            if len(cache) == 3:
373                element._rmap = cache[2]
374        return element
@staticmethod
def make_etree_element(tag: str) -> lxml.etree._Element:
376    @staticmethod
377    def make_etree_element(tag: str) -> _Element:
378        if not isinstance(tag, str):
379            raise TypeError(f"Tag is not str: {tag!r}")
380        tag = tag.strip()
381        if not tag:
382            raise ValueError("Tag is empty")
383        if "<" not in tag:
384            # Qualified name
385            # XXX don't build the element from scratch or lxml will pollute with
386            # repeated namespace declarations
387            tag = f"<{tag}/>"
388        # XML fragment
389        root = fromstring(NAMESPACES_XML % str_to_bytes(tag))
390        return root[0]
tag: str
753    @property
754    def tag(self) -> str:
755        """Get/set the underlying xml tag with the given qualified name.
756
757        Warning: direct change of tag does not change the element class.
758
759        Arguments:
760
761            qname -- str (e.g. "text:span")
762        """
763        return _get_prefixed_name(self.__element.tag)

Get/set the underlying xml tag with the given qualified name.

Warning: direct change of tag does not change the element class.

Arguments:

qname -- str (e.g. "text:span")
def elements_repeated_sequence( self, xpath_instance: lxml.etree.XPath, name: str) -> list[tuple[int, int]]:
769    def elements_repeated_sequence(
770        self,
771        xpath_instance: XPath,
772        name: str,
773    ) -> list[tuple[int, int]]:
774        """Utility method for table module."""
775        lxml_tag = _get_lxml_tag_or_name(name)
776        element = self.__element
777        sub_elements = xpath_instance(element)
778        if not isinstance(sub_elements, list):
779            raise TypeError("Bad XPath result.")
780        result: list[tuple[int, int]] = []
781        idx = -1
782        for sub_element in sub_elements:
783            if not isinstance(sub_element, _Element):
784                continue
785            idx += 1
786            value = sub_element.get(lxml_tag)
787            if value is None:
788                result.append((idx, 1))
789                continue
790            try:
791                int_value = int(value)
792            except ValueError:
793                int_value = 1
794            result.append((idx, max(int_value, 1)))
795        return result

Utility method for table module.

def get_elements(self, xpath_query: lxml.etree.XPath | str) -> list[Element]:
797    def get_elements(self, xpath_query: XPath | str) -> list[Element]:
798        cache: tuple | None = None
799        element = self.__element
800        if isinstance(xpath_query, str):
801            new_xpath_query = xpath_compile(xpath_query)
802            result = new_xpath_query(element)
803        else:
804            result = xpath_query(element)
805        if not isinstance(result, list):
806            raise TypeError("Bad XPath result")
807
808        if hasattr(self, "_tmap"):
809            if hasattr(self, "_rmap"):
810                cache = (self._tmap, self._cmap, self._rmap)
811            else:
812                cache = (self._tmap, self._cmap)
813        return [
814            Element.from_tag_for_clone(e, cache)
815            for e in result
816            if isinstance(e, _Element)
817        ]
def get_element( self, xpath_query: lxml.etree.XPath | str) -> Element | None:
821    def get_element(self, xpath_query: XPath | str) -> Element | None:
822        element = self.__element
823        result = element.xpath(f"({xpath_query})[1]", namespaces=ODF_NAMESPACES)
824        if result:
825            return Element.from_tag(result[0])  # type:ignore
826        return None
attributes: dict[str, str]
842    @property
843    def attributes(self) -> dict[str, str]:
844        return {
845            _get_prefixed_name(str(key)): str(value)
846            for key, value in self.__element.attrib.items()
847        }
def get_attribute(self, name: str) -> str | bool | None:
849    def get_attribute(self, name: str) -> str | bool | None:
850        """Return the attribute value as type str | bool | None."""
851        element = self.__element
852        lxml_tag = _get_lxml_tag_or_name(name)
853        value = element.get(lxml_tag)
854        if value is None:
855            return None
856        elif value in ("true", "false"):
857            return Boolean.decode(value)
858        return str(value)

Return the attribute value as type str | bool | None.

def get_attribute_integer(self, name: str) -> int | None:
860    def get_attribute_integer(self, name: str) -> int | None:
861        """Return either the attribute as type int, or None."""
862        element = self.__element
863        lxml_tag = _get_lxml_tag_or_name(name)
864        value = element.get(lxml_tag)
865        if value is None:
866            return None
867        try:
868            return int(value)
869        except ValueError:
870            return None

Return either the attribute as type int, or None.

def get_attribute_string(self, name: str) -> str | None:
872    def get_attribute_string(self, name: str) -> str | None:
873        """Return either the attribute as type str, or None."""
874        element = self.__element
875        lxml_tag = _get_lxml_tag_or_name(name)
876        value = element.get(lxml_tag)
877        if value is None:
878            return None
879        return str(value)

Return either the attribute as type str, or None.

def set_attribute(self, name: str, value: bool | str | tuple[int, int, int] | None) -> None:
881    def set_attribute(
882        self, name: str, value: bool | str | tuple[int, int, int] | None
883    ) -> None:
884        if name in ODF_COLOR_PROPERTY:
885            if isinstance(value, bool):
886                raise TypeError(f"Wrong color type {value!r}")
887            if value != "transparent":
888                value = hexa_color(value)
889        element = self.__element
890        lxml_tag = _get_lxml_tag_or_name(name)
891        if isinstance(value, bool):
892            value = Boolean.encode(value)
893        elif value is None:
894            with contextlib.suppress(KeyError):
895                del element.attrib[lxml_tag]
896            return
897        element.set(lxml_tag, str(value))
def set_style_attribute(self, name: str, value: Element | str) -> None:
899    def set_style_attribute(self, name: str, value: Element | str) -> None:
900        """Shortcut to accept a style object as a value."""
901        if isinstance(value, Element):
902            value = str(value.name)  # type:ignore
903        return self.set_attribute(name, value)

Shortcut to accept a style object as a value.

def del_attribute(self, name: str) -> None:
905    def del_attribute(self, name: str) -> None:
906        element = self.__element
907        lxml_tag = _get_lxml_tag_or_name(name)
908        del element.attrib[lxml_tag]
text: str
910    @property
911    def text(self) -> str:
912        """Get / set the text content of the element."""
913        return self.__element.text or ""

Get / set the text content of the element.

text_recursive: str
924    @property
925    def text_recursive(self) -> str:
926        return "".join(str(x) for x in self.__element.itertext())
tail: str | None
928    @property
929    def tail(self) -> str | None:
930        """Get / set the text immediately following the element."""
931        return self.__element.tail

Get / set the text immediately following the element.

def search(self, pattern: str) -> int | None:
937    def search(self, pattern: str) -> int | None:
938        """Return the first position of the pattern in the text content of
939        the element, or None if not found.
940
941        Python regular expression syntax applies.
942
943        Arguments:
944
945            pattern -- str
946
947        Return: int or None
948        """
949        match = re.search(pattern, self.text_recursive)
950        if match is None:
951            return None
952        return match.start()

Return the first position of the pattern in the text content of the element, or None if not found.

Python regular expression syntax applies.

Arguments:

pattern -- str

Return: int or None

def match(self, pattern: str) -> bool:
954    def match(self, pattern: str) -> bool:
955        """return True if the pattern is found one or more times anywhere in
956        the text content of the element.
957
958        Python regular expression syntax applies.
959
960        Arguments:
961
962            pattern -- str
963
964        Return: bool
965        """
966        return self.search(pattern) is not None

return True if the pattern is found one or more times anywhere in the text content of the element.

Python regular expression syntax applies.

Arguments:

pattern -- str

Return: bool

def replace(self, pattern: str, new: str | None = None) -> int:
 968    def replace(self, pattern: str, new: str | None = None) -> int:
 969        """Replace the pattern with the given text, or delete if text is an
 970        empty string, and return the number of replacements. By default, only
 971        return the number of occurences that would be replaced.
 972
 973        It cannot replace patterns found across several element, like a word
 974        split into two consecutive spans.
 975
 976        Python regular expression syntax applies.
 977
 978        Arguments:
 979
 980            pattern -- str
 981
 982            new -- str
 983
 984        Return: int
 985        """
 986        if not isinstance(pattern, str):
 987            # Fail properly if the pattern is an non-ascii bytestring
 988            pattern = str(pattern)
 989        cpattern = re.compile(pattern)
 990        count = 0
 991        for text in self.xpath("descendant::text()"):
 992            if new is None:
 993                count += len(cpattern.findall(str(text)))
 994            else:
 995                new_text, number = cpattern.subn(new, str(text))
 996                container = text.parent
 997                if text.is_text():  # type: ignore
 998                    container.text = new_text  # type: ignore
 999                else:
1000                    container.tail = new_text  # type: ignore
1001                count += number
1002        return count

Replace the pattern with the given text, or delete if text is an empty string, and return the number of replacements. By default, only return the number of occurences that would be replaced.

It cannot replace patterns found across several element, like a word split into two consecutive spans.

Python regular expression syntax applies.

Arguments:

pattern -- str

new -- str

Return: int

root: Element
1004    @property
1005    def root(self) -> Element:
1006        element = self.__element
1007        tree = element.getroottree()
1008        root = tree.getroot()
1009        return Element.from_tag(root)
parent: Element | None
1011    @property
1012    def parent(self) -> Element | None:
1013        element = self.__element
1014        parent = element.getparent()
1015        if parent is None:
1016            # Already at root
1017            return None
1018        return Element.from_tag(parent)
is_bound: bool
1020    @property
1021    def is_bound(self) -> bool:
1022        return self.parent is not None
children: list[Element]
1038    @property
1039    def children(self) -> list[Element]:
1040        element = self.__element
1041        return [
1042            Element.from_tag(e)
1043            for e in element.iterchildren()
1044            if isinstance(e, _Element)
1045        ]
def index(self, child: Element) -> int:
1047    def index(self, child: Element) -> int:
1048        """Return the position of the child in this element.
1049
1050        Inspired by lxml
1051        """
1052        return self.__element.index(child.__element)

Return the position of the child in this element.

Inspired by lxml

text_content: str
1054    @property
1055    def text_content(self) -> str:
1056        """Get / set the text of the embedded paragraph, including embeded
1057        annotations, cells...
1058
1059        Set create a paragraph if missing
1060        """
1061        return "\n".join(
1062            child.text_recursive for child in self.get_elements("descendant::text:p")
1063        )

Get / set the text of the embedded paragraph, including embeded annotations, cells...

Set create a paragraph if missing

def is_empty(self) -> bool:
1095    def is_empty(self) -> bool:
1096        """Check if the element is empty : no text, no children, no tail.
1097
1098        Return: Boolean
1099        """
1100        element = self.__element
1101        if element.tail is not None:
1102            return False
1103        if element.text is not None:
1104            return False
1105        if list(element.iterchildren()):
1106            return False
1107        return True

Check if the element is empty : no text, no children, no tail.

Return: Boolean

def get_between( self, tag1: Element, tag2: Element, as_text: bool = False, clean: bool = True, no_header: bool = True) -> list | Element | str:
1252    def get_between(
1253        self,
1254        tag1: Element,
1255        tag2: Element,
1256        as_text: bool = False,
1257        clean: bool = True,
1258        no_header: bool = True,
1259    ) -> list | Element | str:
1260        """Returns elements between tag1 and tag2, tag1 and tag2 shall
1261        be unique and having an id attribute.
1262        (WARN: buggy if tag1/tag2 defines a malformed odf xml.)
1263        If as_text is True: returns the text content.
1264        If clean is True: suppress unwanted tags (deletions marks, ...)
1265        If no_header is True: existing text:h are changed in text:p
1266        By default: returns a list of Element, cleaned and without headers.
1267
1268        Implementation and standard retrictions:
1269        Only text:h and text:p sould be 'cut' by an insert tag, so inner parts
1270        of insert tags are:
1271
1272            - any text:h, text:p or sub tag of these
1273
1274            - some text, part of a parent text:h or text:p
1275
1276        Arguments:
1277
1278            tag1 -- Element
1279
1280            tag2 -- Element
1281
1282            as_text -- boolean
1283
1284            clean -- boolean
1285
1286            no_header -- boolean
1287
1288        Return: list of odf_paragraph or odf_header
1289        """
1290        inner = self._get_between_base(tag1, tag2)
1291
1292        if clean:
1293            clean_tags = (
1294                "text:change",
1295                "text:change-start",
1296                "text:change-end",
1297                "text:reference-mark",
1298                "text:reference-mark-start",
1299                "text:reference-mark-end",
1300            )
1301            request_self = " | ".join(["self::%s" % c for c in clean_tags])
1302            inner = [e for e in inner if not e.xpath(request_self)]
1303            request = " | ".join([f"descendant::{tag}" for tag in clean_tags])
1304            for element in inner:
1305                to_del = element.xpath(request)
1306                for elem in to_del:
1307                    if isinstance(elem, Element):
1308                        element.delete(elem)
1309        if no_header:  # crude replace t:h by t:p
1310            new_inner = []
1311            for element in inner:
1312                if element.tag == "text:h":
1313                    children = element.children
1314                    text = element.__element.text
1315                    para = Element.from_tag("text:p")
1316                    para.text = text or ""
1317                    for c in children:
1318                        para.append(c)
1319                    new_inner.append(para)
1320                else:
1321                    new_inner.append(element)
1322            inner = new_inner
1323        if as_text:
1324            return "\n".join([e.get_formatted_text() for e in inner])
1325        else:
1326            return inner

Returns elements between tag1 and tag2, tag1 and tag2 shall be unique and having an id attribute. (WARN: buggy if tag1/tag2 defines a malformed odf xml.) If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and without headers.

Implementation and standard retrictions: Only text:h and text:p sould be 'cut' by an insert tag, so inner parts of insert tags are:

- any text:h, text:p or sub tag of these

- some text, part of a parent text:h or text:p

Arguments:

tag1 -- Element

tag2 -- Element

as_text -- boolean

clean -- boolean

no_header -- boolean

Return: list of odf_paragraph or odf_header

def insert( self, element: Element, xmlposition: int | None = None, position: int | None = None, start: bool = False) -> None:
1328    def insert(
1329        self,
1330        element: Element,
1331        xmlposition: int | None = None,
1332        position: int | None = None,
1333        start: bool = False,
1334    ) -> None:
1335        """Insert an element relatively to ourself.
1336
1337        Insert either using DOM vocabulary or by numeric position.
1338        If text start is True, insert the element before any existing text.
1339
1340        Position start at 0.
1341
1342        Arguments:
1343
1344            element -- Element
1345
1346            xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
1347                           or PREV_SIBLING
1348
1349            start -- Boolean
1350
1351            position -- int
1352        """
1353        # child_tag = element.tag
1354        current = self.__element
1355        _element = element.__element
1356        if start:
1357            text = current.text
1358            if text is not None:
1359                current.text = None
1360                tail = _element.tail
1361                if tail is None:
1362                    tail = text
1363                else:
1364                    tail = tail + text
1365                _element.tail = tail
1366            position = 0
1367        if position is not None:
1368            current.insert(position, _element)
1369        elif xmlposition is FIRST_CHILD:
1370            current.insert(0, _element)
1371        elif xmlposition is LAST_CHILD:
1372            current.append(_element)
1373        elif xmlposition is NEXT_SIBLING:
1374            parent = current.getparent()
1375            index = parent.index(current)  # type: ignore
1376            parent.insert(index + 1, _element)  # type: ignore
1377        elif xmlposition is PREV_SIBLING:
1378            parent = current.getparent()
1379            index = parent.index(current)  # type: ignore
1380            parent.insert(index, _element)  # type: ignore
1381        else:
1382            raise ValueError("(xml)position must be defined")

Insert an element relatively to ourself.

Insert either using DOM vocabulary or by numeric position. If text start is True, insert the element before any existing text.

Position start at 0.

Arguments:

element -- Element

xmlposition -- FIRST_CHILD, LAST_CHILD, NEXT_SIBLING
               or PREV_SIBLING

start -- Boolean

position -- int
def extend( self, odf_elements: collections.abc.Iterable[Element]) -> None:
1384    def extend(self, odf_elements: Iterable[Element]) -> None:
1385        """Fast append elements at the end of ourself using extend."""
1386        if odf_elements:
1387            current = self.__element
1388            elements = [element.__element for element in odf_elements]
1389            current.extend(elements)

Fast append elements at the end of ourself using extend.

def append(self, str_or_element: str | Element) -> None:
1391    def append(self, str_or_element: str | Element) -> None:
1392        """Insert element or text in the last position."""
1393        current = self.__element
1394        if isinstance(str_or_element, str):
1395            # Has children ?
1396            children = list(current.iterchildren())
1397            if children:
1398                # Append to tail of the last child
1399                last_child = children[-1]
1400                text = last_child.tail
1401                text = text if text is not None else ""
1402                text += str_or_element
1403                last_child.tail = text
1404            else:
1405                # Append to text of the element
1406                text = current.text
1407                text = text if text is not None else ""
1408                text += str_or_element
1409                current.text = text
1410        elif isinstance(str_or_element, Element):
1411            current.append(str_or_element.__element)
1412        else:
1413            raise TypeError(f'Element or string expected, not "{type(str_or_element)}"')

Insert element or text in the last position.

def delete( self, child: Element | None = None, keep_tail: bool = True) -> None:
1415    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
1416        """Delete the given element from the XML tree. If no element is given,
1417        "self" is deleted. The XML library may allow to continue to use an
1418        element now "orphan" as long as you have a reference to it.
1419
1420        if keep_tail is True (default), the tail text is not erased.
1421
1422        Arguments:
1423
1424            child -- Element
1425
1426            keep_tail -- boolean (default to True), True for most usages.
1427        """
1428        if child is None:
1429            parent = self.parent
1430            if parent is None:
1431                raise ValueError(f"Can't delete the root element\n{self.serialize()}")
1432            child = self
1433        else:
1434            parent = self
1435        if keep_tail and child.__element.tail is not None:
1436            current = child.__element
1437            tail = str(current.tail)
1438            current.tail = None
1439            prev = current.getprevious()
1440            if prev is not None:
1441                if prev.tail is None:
1442                    prev.tail = tail
1443                else:
1444                    prev.tail += tail
1445            else:
1446                if parent.__element.text is None:
1447                    parent.__element.text = tail
1448                else:
1449                    parent.__element.text += tail
1450        parent.__element.remove(child.__element)

Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.

if keep_tail is True (default), the tail text is not erased.

Arguments:

child -- Element

keep_tail -- boolean (default to True), True for most usages.
def replace_element( self, old_element: Element, new_element: Element) -> None:
1452    def replace_element(self, old_element: Element, new_element: Element) -> None:
1453        """Replaces in place a sub element with the element passed as second
1454        argument.
1455
1456        Warning : no clone for old element.
1457        """
1458        current = self.__element
1459        current.replace(old_element.__element, new_element.__element)

Replaces in place a sub element with the element passed as second argument.

Warning : no clone for old element.

def strip_elements( self, sub_elements: Element | collections.abc.Iterable[Element]) -> Element | list:
1461    def strip_elements(
1462        self,
1463        sub_elements: Element | Iterable[Element],
1464    ) -> Element | list:
1465        """Remove the tags of provided elements, keeping inner childs and text.
1466
1467        Return : the striped element.
1468
1469        Warning : no clone in sub_elements list.
1470
1471        Arguments:
1472
1473            sub_elements -- Element or list of Element
1474        """
1475        if not sub_elements:
1476            return self
1477        if isinstance(sub_elements, Element):
1478            sub_elements = (sub_elements,)
1479        replacer = _get_lxml_tag("text:this-will-be-removed")
1480        for element in sub_elements:
1481            element.__element.tag = replacer
1482        strip = ("text:this-will-be-removed",)
1483        return self.strip_tags(strip=strip, default=None)

Remove the tags of provided elements, keeping inner childs and text.

Return : the striped element.

Warning : no clone in sub_elements list.

Arguments:

sub_elements -- Element or list of Element
def strip_tags( self, strip: collections.abc.Iterable[str] | None = None, protect: collections.abc.Iterable[str] | None = None, default: str | None = 'text:p') -> Element | list:
1485    def strip_tags(
1486        self,
1487        strip: Iterable[str] | None = None,
1488        protect: Iterable[str] | None = None,
1489        default: str | None = "text:p",
1490    ) -> Element | list:
1491        """Remove the tags listed in strip, recursively, keeping inner childs
1492        and text. Tags listed in protect stop the removal one level depth. If
1493        the first level element is stripped, default is used to embed the
1494        content in the default element. If default is None and first level is
1495        striped, a list of text and children is returned. Return : the striped
1496        element.
1497
1498        strip_tags should be used by on purpose methods (strip_span ...)
1499        (Method name taken from lxml).
1500
1501        Arguments:
1502
1503            strip -- iterable list of str odf tags, or None
1504
1505            protect -- iterable list of str odf tags, or None
1506
1507            default -- str odf tag, or None
1508
1509        Return:
1510
1511            Element.
1512        """
1513        if not strip:
1514            return self
1515        if not protect:
1516            protect = ()
1517        protected = False
1518        element, modified = Element._strip_tags(self, strip, protect, protected)
1519        if modified and isinstance(element, list) and default:
1520            new = Element.from_tag(default)
1521            for content in element:
1522                if isinstance(content, Element):
1523                    new.append(content)
1524                else:
1525                    new.text = content
1526            element = new
1527        return element

Remove the tags listed in strip, recursively, keeping inner childs and text. Tags listed in protect stop the removal one level depth. If the first level element is stripped, default is used to embed the content in the default element. If default is None and first level is striped, a list of text and children is returned. Return : the striped element.

strip_tags should be used by on purpose methods (strip_span ...) (Method name taken from lxml).

Arguments:

strip -- iterable list of str odf tags, or None

protect -- iterable list of str odf tags, or None

default -- str odf tag, or None

Return:

Element.
def xpath( self, xpath_query: str) -> list[Element | Text]:
1583    def xpath(self, xpath_query: str) -> list[Element | Text]:
1584        """Apply XPath query to the element and its subtree. Return list of
1585        Element or Text instances translated from the nodes found.
1586        """
1587        element = self.__element
1588        xpath_instance = xpath_compile(xpath_query)
1589        elements = xpath_instance(element)
1590        result: list[Element | Text] = []
1591        if hasattr(elements, "__iter__"):
1592            for obj in elements:  # type: ignore
1593                if isinstance(obj, (_ElementStringResult, _ElementUnicodeResult)):
1594                    result.append(Text(obj))
1595                elif isinstance(obj, _Element):
1596                    result.append(Element.from_tag(obj))
1597                # else:
1598                #     result.append(obj)
1599        return result

Apply XPath query to the element and its subtree. Return list of Element or Text instances translated from the nodes found.

def clear(self) -> None:
1601    def clear(self) -> None:
1602        """Remove text, children and attributes from the element."""
1603        self.__element.clear()
1604        if hasattr(self, "_tmap"):
1605            self._tmap: list[int] = []
1606        if hasattr(self, "_cmap"):
1607            self._cmap: list[int] = []
1608        if hasattr(self, "_rmap"):
1609            self._rmap: list[int] = []
1610        if hasattr(self, "_indexes"):
1611            remember = False
1612            if "_rmap" in self._indexes:
1613                remember = True
1614            self._indexes: dict[str, dict] = {}
1615            self._indexes["_cmap"] = {}
1616            self._indexes["_tmap"] = {}
1617            if remember:
1618                self._indexes["_rmap"] = {}

Remove text, children and attributes from the element.

clone: Element
1620    @property
1621    def clone(self) -> Element:
1622        clone = deepcopy(self.__element)
1623        root = lxml_Element("ROOT", nsmap=ODF_NAMESPACES)
1624        root.append(clone)
1625        return self.from_tag(clone)
1626
1627        # slow data = tostring(self.__element, encoding='unicode')
1628        # return self.from_tag(data)
def serialize(self, pretty: bool = False, with_ns: bool = False) -> str:
1635    def serialize(self, pretty: bool = False, with_ns: bool = False) -> str:
1636        """Return text serialization of XML element."""
1637        # This copy bypasses serialization side-effects in lxml
1638        native = deepcopy(self.__element)
1639        data = tostring(
1640            native, with_tail=False, pretty_print=pretty, encoding="unicode"
1641        )
1642        if with_ns:
1643            return data
1644        # Remove namespaces
1645        return self._strip_namespaces(data)

Return text serialization of XML element.

document_body: Element | None
1649    @property
1650    def document_body(self) -> Element | None:
1651        """Return the document body : 'office:body'"""
1652        return self.get_element("//office:body/*[1]")

Return the document body : 'office:body'

def get_formatted_text(self, context: dict | None = None) -> str:
1667    def get_formatted_text(self, context: dict | None = None) -> str:
1668        """This function should return a beautiful version of the text."""
1669        return ""

This function should return a beautiful version of the text.

def get_styled_elements(self, name: str = '') -> list[Element]:
1671    def get_styled_elements(self, name: str = "") -> list[Element]:
1672        """Brute-force to find paragraphs, tables, etc. using the given style
1673        name (or all by default).
1674
1675        Arguments:
1676
1677            name -- str
1678
1679        Return: list
1680        """
1681        # FIXME incomplete (and possibly inaccurate)
1682        return (
1683            self._filtered_elements("descendant::*", text_style=name)
1684            + self._filtered_elements("descendant::*", draw_style=name)
1685            + self._filtered_elements("descendant::*", draw_text_style=name)
1686            + self._filtered_elements("descendant::*", table_style=name)
1687            + self._filtered_elements("descendant::*", page_layout=name)
1688            + self._filtered_elements("descendant::*", master_page=name)
1689            + self._filtered_elements("descendant::*", parent_style=name)
1690        )

Brute-force to find paragraphs, tables, etc. using the given style name (or all by default).

Arguments:

name -- str

Return: list

dc_creator: str | None
1709    @property
1710    def dc_creator(self) -> str | None:
1711        """Get dc:creator value.
1712
1713        Return: str (or None if inexistant)
1714        """
1715        return self._get_inner_text("dc:creator")

Get dc:creator value.

Return: str (or None if inexistant)

dc_date: datetime.datetime | None
1727    @property
1728    def dc_date(self) -> datetime | None:
1729        """Get the dc:date value.
1730
1731        Return: datetime (or None if inexistant)
1732        """
1733        date = self._get_inner_text("dc:date")
1734        if date is None:
1735            return None
1736        return DateTime.decode(date)

Get the dc:date value.

Return: datetime (or None if inexistant)

svg_title: str | None
1750    @property
1751    def svg_title(self) -> str | None:
1752        return self._get_inner_text("svg:title")
svg_description: str | None
1758    @property
1759    def svg_description(self) -> str | None:
1760        return self._get_inner_text("svg:desc")
def get_sections( self, style: str | None = None, content: str | None = None) -> list[Element]:
1768    def get_sections(
1769        self,
1770        style: str | None = None,
1771        content: str | None = None,
1772    ) -> list[Element]:
1773        """Return all the sections that match the criteria.
1774
1775        Arguments:
1776
1777            style -- str
1778
1779            content -- str regex
1780
1781        Return: list of Element
1782        """
1783        return self._filtered_elements(
1784            "text:section", text_style=style, content=content
1785        )

Return all the sections that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Element

def get_section( self, position: int = 0, content: str | None = None) -> Element | None:
1787    def get_section(
1788        self,
1789        position: int = 0,
1790        content: str | None = None,
1791    ) -> Element | None:
1792        """Return the section that matches the criteria.
1793
1794        Arguments:
1795
1796            position -- int
1797
1798            content -- str regex
1799
1800        Return: Element or None if not found
1801        """
1802        return self._filtered_element(
1803            "descendant::text:section", position, content=content
1804        )

Return the section that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: Element or None if not found

def get_paragraphs( self, style: str | None = None, content: str | None = None) -> list[Element]:
1808    def get_paragraphs(
1809        self,
1810        style: str | None = None,
1811        content: str | None = None,
1812    ) -> list[Element]:
1813        """Return all the paragraphs that match the criteria.
1814
1815        Arguments:
1816
1817            style -- str
1818
1819            content -- str regex
1820
1821        Return: list of Paragraph
1822        """
1823        return self._filtered_elements(
1824            "descendant::text:p", text_style=style, content=content
1825        )

Return all the paragraphs that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Paragraph

def get_paragraph( self, position: int = 0, content: str | None = None) -> Element | None:
1827    def get_paragraph(
1828        self,
1829        position: int = 0,
1830        content: str | None = None,
1831    ) -> Element | None:
1832        """Return the paragraph that matches the criteria.
1833
1834        Arguments:
1835
1836            position -- int
1837
1838            content -- str regex
1839
1840        Return: Paragraph or None if not found
1841        """
1842        return self._filtered_element("descendant::text:p", position, content=content)

Return the paragraph that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: Paragraph or None if not found

def get_spans( self, style: str | None = None, content: str | None = None) -> list[Element]:
1846    def get_spans(
1847        self,
1848        style: str | None = None,
1849        content: str | None = None,
1850    ) -> list[Element]:
1851        """Return all the spans that match the criteria.
1852
1853        Arguments:
1854
1855            style -- str
1856
1857            content -- str regex
1858
1859        Return: list of Span
1860        """
1861        return self._filtered_elements(
1862            "descendant::text:span", text_style=style, content=content
1863        )

Return all the spans that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Span

def get_span( self, position: int = 0, content: str | None = None) -> Element | None:
1865    def get_span(
1866        self,
1867        position: int = 0,
1868        content: str | None = None,
1869    ) -> Element | None:
1870        """Return the span that matches the criteria.
1871
1872        Arguments:
1873
1874            position -- int
1875
1876            content -- str regex
1877
1878        Return: Span or None if not found
1879        """
1880        return self._filtered_element(
1881            "descendant::text:span", position, content=content
1882        )

Return the span that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: Span or None if not found

def get_headers( self, style: str | None = None, outline_level: str | None = None, content: str | None = None) -> list[Element]:
1886    def get_headers(
1887        self,
1888        style: str | None = None,
1889        outline_level: str | None = None,
1890        content: str | None = None,
1891    ) -> list[Element]:
1892        """Return all the Headers that match the criteria.
1893
1894        Arguments:
1895
1896            style -- str
1897
1898            content -- str regex
1899
1900        Return: list of Header
1901        """
1902        return self._filtered_elements(
1903            "descendant::text:h",
1904            text_style=style,
1905            outline_level=outline_level,
1906            content=content,
1907        )

Return all the Headers that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Header

def get_header( self, position: int = 0, outline_level: str | None = None, content: str | None = None) -> Element | None:
1909    def get_header(
1910        self,
1911        position: int = 0,
1912        outline_level: str | None = None,
1913        content: str | None = None,
1914    ) -> Element | None:
1915        """Return the Header that matches the criteria.
1916
1917        Arguments:
1918
1919            position -- int
1920
1921            content -- str regex
1922
1923        Return: Header or None if not found
1924        """
1925        return self._filtered_element(
1926            "descendant::text:h",
1927            position,
1928            outline_level=outline_level,
1929            content=content,
1930        )

Return the Header that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: Header or None if not found

def get_lists( self, style: str | None = None, content: str | None = None) -> list[Element]:
1934    def get_lists(
1935        self,
1936        style: str | None = None,
1937        content: str | None = None,
1938    ) -> list[Element]:
1939        """Return all the lists that match the criteria.
1940
1941        Arguments:
1942
1943            style -- str
1944
1945            content -- str regex
1946
1947        Return: list of List
1948        """
1949        return self._filtered_elements(
1950            "descendant::text:list", text_style=style, content=content
1951        )

Return all the lists that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of List

def get_list( self, position: int = 0, content: str | None = None) -> Element | None:
1953    def get_list(
1954        self,
1955        position: int = 0,
1956        content: str | None = None,
1957    ) -> Element | None:
1958        """Return the list that matches the criteria.
1959
1960        Arguments:
1961
1962            position -- int
1963
1964            content -- str regex
1965
1966        Return: List or None if not found
1967        """
1968        return self._filtered_element(
1969            "descendant::text:list", position, content=content
1970        )

Return the list that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: List or None if not found

def get_frames( self, presentation_class: str | None = None, style: str | None = None, title: str | None = None, description: str | None = None, content: str | None = None) -> list[Element]:
1974    def get_frames(
1975        self,
1976        presentation_class: str | None = None,
1977        style: str | None = None,
1978        title: str | None = None,
1979        description: str | None = None,
1980        content: str | None = None,
1981    ) -> list[Element]:
1982        """Return all the frames that match the criteria.
1983
1984        Arguments:
1985
1986            presentation_class -- str
1987
1988            style -- str
1989
1990            title -- str regex
1991
1992            description -- str regex
1993
1994            content -- str regex
1995
1996        Return: list of Frame
1997        """
1998        return self._filtered_elements(
1999            "descendant::draw:frame",
2000            presentation_class=presentation_class,
2001            draw_style=style,
2002            svg_title=title,
2003            svg_desc=description,
2004            content=content,
2005        )

Return all the frames that match the criteria.

Arguments:

presentation_class -- str

style -- str

title -- str regex

description -- str regex

content -- str regex

Return: list of Frame

def get_frame( self, position: int = 0, name: str | None = None, presentation_class: str | None = None, title: str | None = None, description: str | None = None, content: str | None = None) -> Element | None:
2007    def get_frame(
2008        self,
2009        position: int = 0,
2010        name: str | None = None,
2011        presentation_class: str | None = None,
2012        title: str | None = None,
2013        description: str | None = None,
2014        content: str | None = None,
2015    ) -> Element | None:
2016        """Return the section that matches the criteria.
2017
2018        Arguments:
2019
2020            position -- int
2021
2022            name -- str
2023
2024            presentation_class -- str
2025
2026            title -- str regex
2027
2028            description -- str regex
2029
2030            content -- str regex
2031
2032        Return: Frame or None if not found
2033        """
2034        return self._filtered_element(
2035            "descendant::draw:frame",
2036            position,
2037            draw_name=name,
2038            presentation_class=presentation_class,
2039            svg_title=title,
2040            svg_desc=description,
2041            content=content,
2042        )

Return the section that matches the criteria.

Arguments:

position -- int

name -- str

presentation_class -- str

title -- str regex

description -- str regex

content -- str regex

Return: Frame or None if not found

def get_images( self, style: str | None = None, url: str | None = None, content: str | None = None) -> list[Element]:
2046    def get_images(
2047        self,
2048        style: str | None = None,
2049        url: str | None = None,
2050        content: str | None = None,
2051    ) -> list[Element]:
2052        """Return all the images matching the criteria.
2053
2054        Arguments:
2055
2056            style -- str
2057
2058            url -- str regex
2059
2060            content -- str regex
2061
2062        Return: list of Element
2063        """
2064        return self._filtered_elements(
2065            "descendant::draw:image", text_style=style, url=url, content=content
2066        )

Return all the images matching the criteria.

Arguments:

style -- str

url -- str regex

content -- str regex

Return: list of Element

def get_image( self, position: int = 0, name: str | None = None, url: str | None = None, content: str | None = None) -> Element | None:
2068    def get_image(
2069        self,
2070        position: int = 0,
2071        name: str | None = None,
2072        url: str | None = None,
2073        content: str | None = None,
2074    ) -> Element | None:
2075        """Return the image matching the criteria.
2076
2077        Arguments:
2078
2079            position -- int
2080
2081            name -- str
2082
2083            url -- str regex
2084
2085            content -- str regex
2086
2087        Return: Element or None if not found
2088        """
2089        # The frame is holding the name
2090        if name is not None:
2091            frame = self._filtered_element(
2092                "descendant::draw:frame", position, draw_name=name
2093            )
2094            if frame is None:
2095                return None
2096            # The name is supposedly unique
2097            return frame.get_element("draw:image")
2098        return self._filtered_element(
2099            "descendant::draw:image", position, url=url, content=content
2100        )

Return the image matching the criteria.

Arguments:

position -- int

name -- str

url -- str regex

content -- str regex

Return: Element or None if not found

def get_tables( self, style: str | None = None, content: str | None = None) -> list[Element]:
2104    def get_tables(
2105        self,
2106        style: str | None = None,
2107        content: str | None = None,
2108    ) -> list[Element]:
2109        """Return all the tables that match the criteria.
2110
2111        Arguments:
2112
2113            style -- str
2114
2115            content -- str regex
2116
2117        Return: list of Table
2118        """
2119        return self._filtered_elements(
2120            "descendant::table:table", table_style=style, content=content
2121        )

Return all the tables that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Table

def get_table( self, position: int = 0, name: str | None = None, content: str | None = None) -> Element | None:
2123    def get_table(
2124        self,
2125        position: int = 0,
2126        name: str | None = None,
2127        content: str | None = None,
2128    ) -> Element | None:
2129        """Return the table that matches the criteria.
2130
2131        Arguments:
2132
2133            position -- int
2134
2135            name -- str
2136
2137            content -- str regex
2138
2139        Return: Table or None if not found
2140        """
2141        if name is None and content is None:
2142            result = self._filtered_element("descendant::table:table", position)
2143        else:
2144            result = self._filtered_element(
2145                "descendant::table:table",
2146                position,
2147                table_name=name,
2148                content=content,
2149            )
2150        return result

Return the table that matches the criteria.

Arguments:

position -- int

name -- str

content -- str regex

Return: Table or None if not found

def get_named_ranges(self) -> list[Element]:
2154    def get_named_ranges(self) -> list[Element]:
2155        """Return all the tables named ranges.
2156
2157        Return: list of odf_named_range
2158        """
2159        named_ranges = self.get_elements(
2160            "descendant::table:named-expressions/table:named-range"
2161        )
2162        return named_ranges

Return all the tables named ranges.

Return: list of odf_named_range

def get_named_range(self, name: str) -> Element | None:
2164    def get_named_range(self, name: str) -> Element | None:
2165        """Return the named range of specified name, or None if not found.
2166
2167        Arguments:
2168
2169            name -- str
2170
2171        Return: NamedRange
2172        """
2173        named_range = self.get_elements(
2174            f'descendant::table:named-expressions/table:named-range[@table:name="{name}"][1]'
2175        )
2176        if named_range:
2177            return named_range[0]
2178        else:
2179            return None

Return the named range of specified name, or None if not found.

Arguments:

name -- str

Return: NamedRange

def append_named_range(self, named_range: Element) -> None:
2181    def append_named_range(self, named_range: Element) -> None:
2182        """Append the named range to the spreadsheet, replacing existing named
2183        range of same name if any.
2184
2185        Arguments:
2186
2187            named_range --  NamedRange
2188        """
2189        if self.tag != "office:spreadsheet":
2190            raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}")
2191        named_expressions = self.get_element("table:named-expressions")
2192        if not named_expressions:
2193            named_expressions = Element.from_tag("table:named-expressions")
2194            self.append(named_expressions)
2195        # exists ?
2196        current = named_expressions.get_element(
2197            f'table:named-range[@table:name="{named_range.name}"][1]'  # type:ignore
2198        )
2199        if current:
2200            named_expressions.delete(current)
2201        named_expressions.append(named_range)

Append the named range to the spreadsheet, replacing existing named range of same name if any.

Arguments:

named_range --  NamedRange
def delete_named_range(self, name: str) -> None:
2203    def delete_named_range(self, name: str) -> None:
2204        """Delete the Named Range of specified name from the spreadsheet.
2205
2206        Arguments:
2207
2208            name -- str
2209        """
2210        if self.tag != "office:spreadsheet":
2211            raise ValueError(f"Element is no 'office:spreadsheet' : {self.tag}")
2212        named_range = self.get_named_range(name)
2213        if not named_range:
2214            return
2215        named_range.delete()
2216        named_expressions = self.get_element("table:named-expressions")
2217        if not named_expressions:
2218            return
2219        element = named_expressions.__element
2220        children = list(element.iterchildren())
2221        if not children:
2222            self.delete(named_expressions)

Delete the Named Range of specified name from the spreadsheet.

Arguments:

name -- str
def get_notes( self, note_class: str | None = None, content: str | None = None) -> list[Element]:
2226    def get_notes(
2227        self,
2228        note_class: str | None = None,
2229        content: str | None = None,
2230    ) -> list[Element]:
2231        """Return all the notes that match the criteria.
2232
2233        Arguments:
2234
2235            note_class -- 'footnote' or 'endnote'
2236
2237            content -- str regex
2238
2239        Return: list of Note
2240        """
2241        return self._filtered_elements(
2242            "descendant::text:note", note_class=note_class, content=content
2243        )

Return all the notes that match the criteria.

Arguments:

note_class -- 'footnote' or 'endnote'

content -- str regex

Return: list of Note

def get_note( self, position: int = 0, note_id: str | None = None, note_class: str | None = None, content: str | None = None) -> Element | None:
2245    def get_note(
2246        self,
2247        position: int = 0,
2248        note_id: str | None = None,
2249        note_class: str | None = None,
2250        content: str | None = None,
2251    ) -> Element | None:
2252        """Return the note that matches the criteria.
2253
2254        Arguments:
2255
2256            position -- int
2257
2258            note_id -- str
2259
2260            note_class -- 'footnote' or 'endnote'
2261
2262            content -- str regex
2263
2264        Return: Note or None if not found
2265        """
2266        return self._filtered_element(
2267            "descendant::text:note",
2268            position,
2269            text_id=note_id,
2270            note_class=note_class,
2271            content=content,
2272        )

Return the note that matches the criteria.

Arguments:

position -- int

note_id -- str

note_class -- 'footnote' or 'endnote'

content -- str regex

Return: Note or None if not found

def get_annotations( self, creator: str | None = None, start_date: datetime.datetime | None = None, end_date: datetime.datetime | None = None, content: str | None = None) -> list[Element]:
2276    def get_annotations(
2277        self,
2278        creator: str | None = None,
2279        start_date: datetime | None = None,
2280        end_date: datetime | None = None,
2281        content: str | None = None,
2282    ) -> list[Element]:
2283        """Return all the annotations that match the criteria.
2284
2285        Arguments:
2286
2287            creator -- str
2288
2289            start_date -- datetime instance
2290
2291            end_date --  datetime instance
2292
2293            content -- str regex
2294
2295        Return: list of Annotation
2296        """
2297        annotations = []
2298        for annotation in self._filtered_elements(
2299            "descendant::office:annotation", content=content
2300        ):
2301            if creator is not None and creator != annotation.dc_creator:
2302                continue
2303            date = annotation.dc_date
2304            if date is None:
2305                continue
2306            if start_date is not None and date < start_date:
2307                continue
2308            if end_date is not None and date >= end_date:
2309                continue
2310            annotations.append(annotation)
2311        return annotations

Return all the annotations that match the criteria.

Arguments:

creator -- str

start_date -- datetime instance

end_date --  datetime instance

content -- str regex

Return: list of Annotation

def get_annotation( self, position: int = 0, creator: str | None = None, start_date: datetime.datetime | None = None, end_date: datetime.datetime | None = None, content: str | None = None, name: str | None = None) -> Element | None:
2313    def get_annotation(
2314        self,
2315        position: int = 0,
2316        creator: str | None = None,
2317        start_date: datetime | None = None,
2318        end_date: datetime | None = None,
2319        content: str | None = None,
2320        name: str | None = None,
2321    ) -> Element | None:
2322        """Return the annotation that matches the criteria.
2323
2324        Arguments:
2325
2326            position -- int
2327
2328            creator -- str
2329
2330            start_date -- datetime instance
2331
2332            end_date -- datetime instance
2333
2334            content -- str regex
2335
2336            name -- str
2337
2338        Return: Annotation or None if not found
2339        """
2340        if name is not None:
2341            return self._filtered_element(
2342                "descendant::office:annotation", 0, office_name=name
2343            )
2344        annotations = self.get_annotations(
2345            creator=creator, start_date=start_date, end_date=end_date, content=content
2346        )
2347        if not annotations:
2348            return None
2349        try:
2350            return annotations[position]
2351        except IndexError:
2352            return None

Return the annotation that matches the criteria.

Arguments:

position -- int

creator -- str

start_date -- datetime instance

end_date -- datetime instance

content -- str regex

name -- str

Return: Annotation or None if not found

def get_annotation_ends(self) -> list[Element]:
2354    def get_annotation_ends(self) -> list[Element]:
2355        """Return all the annotation ends.
2356
2357        Return: list of Element
2358        """
2359        return self._filtered_elements("descendant::office:annotation-end")

Return all the annotation ends.

Return: list of Element

def get_annotation_end( self, position: int = 0, name: str | None = None) -> Element | None:
2361    def get_annotation_end(
2362        self,
2363        position: int = 0,
2364        name: str | None = None,
2365    ) -> Element | None:
2366        """Return the annotation end that matches the criteria.
2367
2368        Arguments:
2369
2370            position -- int
2371
2372            name -- str
2373
2374        Return: Element or None if not found
2375        """
2376        return self._filtered_element(
2377            "descendant::office:annotation-end", position, office_name=name
2378        )

Return the annotation end that matches the criteria.

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_office_names(self) -> list[str]:
2382    def get_office_names(self) -> list[str]:
2383        """Return all the used office:name tags values of the element.
2384
2385        Return: list of unique str
2386        """
2387        name_xpath_query = xpath_compile("//@office:name")
2388        response = name_xpath_query(self.__element)
2389        if not isinstance(response, list):
2390            return []
2391        return list({str(name) for name in response if name})

Return all the used office:name tags values of the element.

Return: list of unique str

def get_variable_decls(self) -> Element:
2395    def get_variable_decls(self) -> Element:
2396        """Return the container for variable declarations. Created if not
2397        found.
2398
2399        Return: Element
2400        """
2401        variable_decls = self.get_element("//text:variable-decls")
2402        if variable_decls is None:
2403            body = self.document_body
2404            if not body:
2405                raise ValueError("Empty document.body")
2406            body.insert(Element.from_tag("text:variable-decls"), FIRST_CHILD)
2407            variable_decls = body.get_element("//text:variable-decls")
2408
2409        return variable_decls  # type:ignore

Return the container for variable declarations. Created if not found.

Return: Element

def get_variable_decl_list(self) -> list[Element]:
2411    def get_variable_decl_list(self) -> list[Element]:
2412        """Return all the variable declarations.
2413
2414        Return: list of Element
2415        """
2416        return self._filtered_elements("descendant::text:variable-decl")

Return all the variable declarations.

Return: list of Element

def get_variable_decl(self, name: str, position: int = 0) -> Element | None:
2418    def get_variable_decl(self, name: str, position: int = 0) -> Element | None:
2419        """return the variable declaration for the given name.
2420
2421        Arguments:
2422
2423            name -- str
2424
2425            position -- int
2426
2427        return: Element or none if not found
2428        """
2429        return self._filtered_element(
2430            "descendant::text:variable-decl", position, text_name=name
2431        )

return the variable declaration for the given name.

Arguments:

name -- str

position -- int

return: Element or none if not found

def get_variable_sets(self, name: str | None = None) -> list[Element]:
2433    def get_variable_sets(self, name: str | None = None) -> list[Element]:
2434        """Return all the variable sets that match the criteria.
2435
2436        Arguments:
2437
2438            name -- str
2439
2440        Return: list of Element
2441        """
2442        return self._filtered_elements("descendant::text:variable-set", text_name=name)

Return all the variable sets that match the criteria.

Arguments:

name -- str

Return: list of Element

def get_variable_set(self, name: str, position: int = -1) -> Element | None:
2444    def get_variable_set(self, name: str, position: int = -1) -> Element | None:
2445        """Return the variable set for the given name (last one by default).
2446
2447        Arguments:
2448
2449            name -- str
2450
2451            position -- int
2452
2453        Return: Element or None if not found
2454        """
2455        return self._filtered_element(
2456            "descendant::text:variable-set", position, text_name=name
2457        )

Return the variable set for the given name (last one by default).

Arguments:

name -- str

position -- int

Return: Element or None if not found

def get_variable_set_value( self, name: str, value_type: str | None = None) -> bool | str | int | float | decimal.Decimal | datetime.datetime | datetime.timedelta | None:
2459    def get_variable_set_value(
2460        self,
2461        name: str,
2462        value_type: str | None = None,
2463    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2464        """Return the last value of the given variable name.
2465
2466        Arguments:
2467
2468            name -- str
2469
2470            value_type -- 'boolean', 'currency', 'date', 'float',
2471                          'percentage', 'string', 'time' or automatic
2472
2473        Return: most appropriate Python type
2474        """
2475        variable_set = self.get_variable_set(name)
2476        if not variable_set:
2477            return None
2478        return variable_set.get_value(value_type)  # type: ignore

Return the last value of the given variable name.

Arguments:

name -- str

value_type -- 'boolean', 'currency', 'date', 'float',
              'percentage', 'string', 'time' or automatic

Return: most appropriate Python type

def get_user_field_decls(self) -> Element | None:
2482    def get_user_field_decls(self) -> Element | None:
2483        """Return the container for user field declarations. Created if not
2484        found.
2485
2486        Return: Element
2487        """
2488        user_field_decls = self.get_element("//text:user-field-decls")
2489        if user_field_decls is None:
2490            body = self.document_body
2491            if not body:
2492                raise ValueError("Empty document.body")
2493            body.insert(Element.from_tag("text:user-field-decls"), FIRST_CHILD)
2494            user_field_decls = body.get_element("//text:user-field-decls")
2495
2496        return user_field_decls

Return the container for user field declarations. Created if not found.

Return: Element

def get_user_field_decl_list(self) -> list[Element]:
2498    def get_user_field_decl_list(self) -> list[Element]:
2499        """Return all the user field declarations.
2500
2501        Return: list of Element
2502        """
2503        return self._filtered_elements("descendant::text:user-field-decl")

Return all the user field declarations.

Return: list of Element

def get_user_field_decl(self, name: str, position: int = 0) -> Element | None:
2505    def get_user_field_decl(self, name: str, position: int = 0) -> Element | None:
2506        """return the user field declaration for the given name.
2507
2508        return: Element or none if not found
2509        """
2510        return self._filtered_element(
2511            "descendant::text:user-field-decl", position, text_name=name
2512        )

return the user field declaration for the given name.

return: Element or none if not found

def get_user_field_value( self, name: str, value_type: str | None = None) -> bool | str | int | float | decimal.Decimal | datetime.datetime | datetime.timedelta | None:
2514    def get_user_field_value(
2515        self, name: str, value_type: str | None = None
2516    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2517        """Return the value of the given user field name.
2518
2519        Arguments:
2520
2521            name -- str
2522
2523            value_type -- 'boolean', 'currency', 'date', 'float',
2524                          'percentage', 'string', 'time' or automatic
2525
2526        Return: most appropriate Python type
2527        """
2528        user_field_decl = self.get_user_field_decl(name)
2529        if user_field_decl is None:
2530            return None
2531        return user_field_decl.get_value(value_type)  # type: ignore

Return the value of the given user field name.

Arguments:

name -- str

value_type -- 'boolean', 'currency', 'date', 'float',
              'percentage', 'string', 'time' or automatic

Return: most appropriate Python type

def get_user_defined_list(self) -> list[Element]:
2536    def get_user_defined_list(self) -> list[Element]:
2537        """Return all the user defined field declarations.
2538
2539        Return: list of Element
2540        """
2541        return self._filtered_elements("descendant::text:user-defined")

Return all the user defined field declarations.

Return: list of Element

def get_user_defined(self, name: str, position: int = 0) -> Element | None:
2543    def get_user_defined(self, name: str, position: int = 0) -> Element | None:
2544        """return the user defined declaration for the given name.
2545
2546        return: Element or none if not found
2547        """
2548        return self._filtered_element(
2549            "descendant::text:user-defined", position, text_name=name
2550        )

return the user defined declaration for the given name.

return: Element or none if not found

def get_user_defined_value( self, name: str, value_type: str | None = None) -> bool | str | int | float | decimal.Decimal | datetime.datetime | datetime.timedelta | None:
2552    def get_user_defined_value(
2553        self, name: str, value_type: str | None = None
2554    ) -> bool | str | int | float | Decimal | datetime | timedelta | None:
2555        """Return the value of the given user defined field name.
2556
2557        Arguments:
2558
2559            name -- str
2560
2561            value_type -- 'boolean', 'date', 'float',
2562                          'string', 'time' or automatic
2563
2564        Return: most appropriate Python type
2565        """
2566        user_defined = self.get_user_defined(name)
2567        if user_defined is None:
2568            return None
2569        return user_defined.get_value(value_type)  # type: ignore

Return the value of the given user defined field name.

Arguments:

name -- str

value_type -- 'boolean', 'date', 'float',
              'string', 'time' or automatic

Return: most appropriate Python type

def get_draw_pages( self, style: str | None = None, content: str | None = None) -> list[Element]:
2573    def get_draw_pages(
2574        self,
2575        style: str | None = None,
2576        content: str | None = None,
2577    ) -> list[Element]:
2578        """Return all the draw pages that match the criteria.
2579
2580        Arguments:
2581
2582            style -- str
2583
2584            content -- str regex
2585
2586        Return: list of DrawPage
2587        """
2588        return self._filtered_elements(
2589            "descendant::draw:page", draw_style=style, content=content
2590        )

Return all the draw pages that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of DrawPage

def get_draw_page( self, position: int = 0, name: str | None = None, content: str | None = None) -> Element | None:
2592    def get_draw_page(
2593        self,
2594        position: int = 0,
2595        name: str | None = None,
2596        content: str | None = None,
2597    ) -> Element | None:
2598        """Return the draw page that matches the criteria.
2599
2600        Arguments:
2601
2602            position -- int
2603
2604            name -- str
2605
2606            content -- str regex
2607
2608        Return: DrawPage or None if not found
2609        """
2610        return self._filtered_element(
2611            "descendant::draw:page", position, draw_name=name, content=content
2612        )

Return the draw page that matches the criteria.

Arguments:

position -- int

name -- str

content -- str regex

Return: DrawPage or None if not found

def get_bookmarks(self) -> list[Element]:
2680    def get_bookmarks(self) -> list[Element]:
2681        """Return all the bookmarks.
2682
2683        Return: list of Element
2684        """
2685        return self._filtered_elements("descendant::text:bookmark")

Return all the bookmarks.

Return: list of Element

def get_bookmark( self, position: int = 0, name: str | None = None) -> Element | None:
2687    def get_bookmark(
2688        self,
2689        position: int = 0,
2690        name: str | None = None,
2691    ) -> Element | None:
2692        """Return the bookmark that matches the criteria.
2693
2694        Arguments:
2695
2696            position -- int
2697
2698            name -- str
2699
2700        Return: Bookmark or None if not found
2701        """
2702        return self._filtered_element(
2703            "descendant::text:bookmark", position, text_name=name
2704        )

Return the bookmark that matches the criteria.

Arguments:

position -- int

name -- str

Return: Bookmark or None if not found

def get_bookmark_starts(self) -> list[Element]:
2706    def get_bookmark_starts(self) -> list[Element]:
2707        """Return all the bookmark starts.
2708
2709        Return: list of Element
2710        """
2711        return self._filtered_elements("descendant::text:bookmark-start")

Return all the bookmark starts.

Return: list of Element

def get_bookmark_start( self, position: int = 0, name: str | None = None) -> Element | None:
2713    def get_bookmark_start(
2714        self,
2715        position: int = 0,
2716        name: str | None = None,
2717    ) -> Element | None:
2718        """Return the bookmark start that matches the criteria.
2719
2720        Arguments:
2721
2722            position -- int
2723
2724            name -- str
2725
2726        Return: Element or None if not found
2727        """
2728        return self._filtered_element(
2729            "descendant::text:bookmark-start", position, text_name=name
2730        )

Return the bookmark start that matches the criteria.

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_bookmark_ends(self) -> list[Element]:
2732    def get_bookmark_ends(self) -> list[Element]:
2733        """Return all the bookmark ends.
2734
2735        Return: list of Element
2736        """
2737        return self._filtered_elements("descendant::text:bookmark-end")

Return all the bookmark ends.

Return: list of Element

def get_bookmark_end( self, position: int = 0, name: str | None = None) -> Element | None:
2739    def get_bookmark_end(
2740        self,
2741        position: int = 0,
2742        name: str | None = None,
2743    ) -> Element | None:
2744        """Return the bookmark end that matches the criteria.
2745
2746        Arguments:
2747
2748            position -- int
2749
2750            name -- str
2751
2752        Return: Element or None if not found
2753        """
2754        return self._filtered_element(
2755            "descendant::text:bookmark-end", position, text_name=name
2756        )

Return the bookmark end that matches the criteria.

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_reference_marks_single(self) -> list[Element]:
2760    def get_reference_marks_single(self) -> list[Element]:
2761        """Return all the reference marks. Search only the tags
2762        text:reference-mark.
2763        Consider using : get_reference_marks()
2764
2765        Return: list of Element
2766        """
2767        return self._filtered_elements("descendant::text:reference-mark")

Return all the reference marks. Search only the tags text:reference-mark. Consider using : get_reference_marks()

Return: list of Element

def get_reference_mark_single( self, position: int = 0, name: str | None = None) -> Element | None:
2769    def get_reference_mark_single(
2770        self,
2771        position: int = 0,
2772        name: str | None = None,
2773    ) -> Element | None:
2774        """Return the reference mark that matches the criteria. Search only the
2775        tags text:reference-mark.
2776        Consider using : get_reference_mark()
2777
2778        Arguments:
2779
2780            position -- int
2781
2782            name -- str
2783
2784        Return: Element or None if not found
2785        """
2786        return self._filtered_element(
2787            "descendant::text:reference-mark", position, text_name=name
2788        )

Return the reference mark that matches the criteria. Search only the tags text:reference-mark. Consider using : get_reference_mark()

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_reference_mark_starts(self) -> list[Element]:
2790    def get_reference_mark_starts(self) -> list[Element]:
2791        """Return all the reference mark starts. Search only the tags
2792        text:reference-mark-start.
2793        Consider using : get_reference_marks()
2794
2795        Return: list of Element
2796        """
2797        return self._filtered_elements("descendant::text:reference-mark-start")

Return all the reference mark starts. Search only the tags text:reference-mark-start. Consider using : get_reference_marks()

Return: list of Element

def get_reference_mark_start( self, position: int = 0, name: str | None = None) -> Element | None:
2799    def get_reference_mark_start(
2800        self,
2801        position: int = 0,
2802        name: str | None = None,
2803    ) -> Element | None:
2804        """Return the reference mark start that matches the criteria. Search
2805        only the tags text:reference-mark-start.
2806        Consider using : get_reference_mark()
2807
2808        Arguments:
2809
2810            position -- int
2811
2812            name -- str
2813
2814        Return: Element or None if not found
2815        """
2816        return self._filtered_element(
2817            "descendant::text:reference-mark-start", position, text_name=name
2818        )

Return the reference mark start that matches the criteria. Search only the tags text:reference-mark-start. Consider using : get_reference_mark()

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_reference_mark_ends(self) -> list[Element]:
2820    def get_reference_mark_ends(self) -> list[Element]:
2821        """Return all the reference mark ends. Search only the tags
2822        text:reference-mark-end.
2823        Consider using : get_reference_marks()
2824
2825        Return: list of Element
2826        """
2827        return self._filtered_elements("descendant::text:reference-mark-end")

Return all the reference mark ends. Search only the tags text:reference-mark-end. Consider using : get_reference_marks()

Return: list of Element

def get_reference_mark_end( self, position: int = 0, name: str | None = None) -> Element | None:
2829    def get_reference_mark_end(
2830        self,
2831        position: int = 0,
2832        name: str | None = None,
2833    ) -> Element | None:
2834        """Return the reference mark end that matches the criteria. Search only
2835        the tags text:reference-mark-end.
2836        Consider using : get_reference_marks()
2837
2838        Arguments:
2839
2840            position -- int
2841
2842            name -- str
2843
2844        Return: Element or None if not found
2845        """
2846        return self._filtered_element(
2847            "descendant::text:reference-mark-end", position, text_name=name
2848        )

Return the reference mark end that matches the criteria. Search only the tags text:reference-mark-end. Consider using : get_reference_marks()

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_reference_marks(self) -> list[Element]:
2850    def get_reference_marks(self) -> list[Element]:
2851        """Return all the reference marks, either single position reference
2852        (text:reference-mark) or start of range reference
2853        (text:reference-mark-start).
2854
2855        Return: list of Element
2856        """
2857        return self._filtered_elements(
2858            "descendant::text:reference-mark-start | descendant::text:reference-mark"
2859        )

Return all the reference marks, either single position reference (text:reference-mark) or start of range reference (text:reference-mark-start).

Return: list of Element

def get_reference_mark( self, position: int = 0, name: str | None = None) -> Element | None:
2861    def get_reference_mark(
2862        self,
2863        position: int = 0,
2864        name: str | None = None,
2865    ) -> Element | None:
2866        """Return the reference mark that match the criteria. Either single
2867        position reference mark (text:reference-mark) or start of range
2868        reference (text:reference-mark-start).
2869
2870        Arguments:
2871
2872            position -- int
2873
2874            name -- str
2875
2876        Return: Element or None if not found
2877        """
2878        if name:
2879            request = (
2880                f"descendant::text:reference-mark-start"
2881                f'[@text:name="{name}"] '
2882                f"| descendant::text:reference-mark"
2883                f'[@text:name="{name}"]'
2884            )
2885            return self._filtered_element(request, position=0)
2886        request = (
2887            "descendant::text:reference-mark-start | descendant::text:reference-mark"
2888        )
2889        return self._filtered_element(request, position)

Return the reference mark that match the criteria. Either single position reference mark (text:reference-mark) or start of range reference (text:reference-mark-start).

Arguments:

position -- int

name -- str

Return: Element or None if not found

def get_references(self, name: str | None = None) -> list[Element]:
2891    def get_references(self, name: str | None = None) -> list[Element]:
2892        """Return all the references (text:reference-ref). If name is
2893        provided, returns the references of that name.
2894
2895        Return: list of Element
2896
2897        Arguments:
2898
2899            name -- str or None
2900        """
2901        if name is None:
2902            return self._filtered_elements("descendant::text:reference-ref")
2903        request = f'descendant::text:reference-ref[@text:ref-name="{name}"]'
2904        return self._filtered_elements(request)

Return all the references (text:reference-ref). If name is provided, returns the references of that name.

Return: list of Element

Arguments:

name -- str or None
def get_draw_groups( self, title: str | None = None, description: str | None = None, content: str | None = None) -> list[Element]:
2910    def get_draw_groups(
2911        self,
2912        title: str | None = None,
2913        description: str | None = None,
2914        content: str | None = None,
2915    ) -> list[Element]:
2916        return self._filtered_elements(
2917            "descendant::draw:g",
2918            svg_title=title,
2919            svg_desc=description,
2920            content=content,
2921        )
def get_draw_group( self, position: int = 0, name: str | None = None, title: str | None = None, description: str | None = None, content: str | None = None) -> Element | None:
2923    def get_draw_group(
2924        self,
2925        position: int = 0,
2926        name: str | None = None,
2927        title: str | None = None,
2928        description: str | None = None,
2929        content: str | None = None,
2930    ) -> Element | None:
2931        return self._filtered_element(
2932            "descendant::draw:g",
2933            position,
2934            draw_name=name,
2935            svg_title=title,
2936            svg_desc=description,
2937            content=content,
2938        )
def get_draw_lines( self, draw_style: str | None = None, draw_text_style: str | None = None, content: str | None = None) -> list[Element]:
2942    def get_draw_lines(
2943        self,
2944        draw_style: str | None = None,
2945        draw_text_style: str | None = None,
2946        content: str | None = None,
2947    ) -> list[Element]:
2948        """Return all the draw lines that match the criteria.
2949
2950        Arguments:
2951
2952            draw_style -- str
2953
2954            draw_text_style -- str
2955
2956            content -- str regex
2957
2958        Return: list of odf_shape
2959        """
2960        return self._filtered_elements(
2961            "descendant::draw:line",
2962            draw_style=draw_style,
2963            draw_text_style=draw_text_style,
2964            content=content,
2965        )

Return all the draw lines that match the criteria.

Arguments:

draw_style -- str

draw_text_style -- str

content -- str regex

Return: list of odf_shape

def get_draw_line( self, position: int = 0, id: str | None = None, content: str | None = None) -> Element | None:
2967    def get_draw_line(
2968        self,
2969        position: int = 0,
2970        id: str | None = None,  # noqa:A002
2971        content: str | None = None,
2972    ) -> Element | None:
2973        """Return the draw line that matches the criteria.
2974
2975        Arguments:
2976
2977            position -- int
2978
2979            id -- str
2980
2981            content -- str regex
2982
2983        Return: odf_shape or None if not found
2984        """
2985        return self._filtered_element(
2986            "descendant::draw:line", position, draw_id=id, content=content
2987        )

Return the draw line that matches the criteria.

Arguments:

position -- int

id -- str

content -- str regex

Return: odf_shape or None if not found

def get_draw_rectangles( self, draw_style: str | None = None, draw_text_style: str | None = None, content: str | None = None) -> list[Element]:
2991    def get_draw_rectangles(
2992        self,
2993        draw_style: str | None = None,
2994        draw_text_style: str | None = None,
2995        content: str | None = None,
2996    ) -> list[Element]:
2997        """Return all the draw rectangles that match the criteria.
2998
2999        Arguments:
3000
3001            draw_style -- str
3002
3003            draw_text_style -- str
3004
3005            content -- str regex
3006
3007        Return: list of odf_shape
3008        """
3009        return self._filtered_elements(
3010            "descendant::draw:rect",
3011            draw_style=draw_style,
3012            draw_text_style=draw_text_style,
3013            content=content,
3014        )

Return all the draw rectangles that match the criteria.

Arguments:

draw_style -- str

draw_text_style -- str

content -- str regex

Return: list of odf_shape

def get_draw_rectangle( self, position: int = 0, id: str | None = None, content: str | None = None) -> Element | None:
3016    def get_draw_rectangle(
3017        self,
3018        position: int = 0,
3019        id: str | None = None,  # noqa:A002
3020        content: str | None = None,
3021    ) -> Element | None:
3022        """Return the draw rectangle that matches the criteria.
3023
3024        Arguments:
3025
3026            position -- int
3027
3028            id -- str
3029
3030            content -- str regex
3031
3032        Return: odf_shape or None if not found
3033        """
3034        return self._filtered_element(
3035            "descendant::draw:rect", position, draw_id=id, content=content
3036        )

Return the draw rectangle that matches the criteria.

Arguments:

position -- int

id -- str

content -- str regex

Return: odf_shape or None if not found

def get_draw_ellipses( self, draw_style: str | None = None, draw_text_style: str | None = None, content: str | None = None) -> list[Element]:
3040    def get_draw_ellipses(
3041        self,
3042        draw_style: str | None = None,
3043        draw_text_style: str | None = None,
3044        content: str | None = None,
3045    ) -> list[Element]:
3046        """Return all the draw ellipses that match the criteria.
3047
3048        Arguments:
3049
3050            draw_style -- str
3051
3052            draw_text_style -- str
3053
3054            content -- str regex
3055
3056        Return: list of odf_shape
3057        """
3058        return self._filtered_elements(
3059            "descendant::draw:ellipse",
3060            draw_style=draw_style,
3061            draw_text_style=draw_text_style,
3062            content=content,
3063        )

Return all the draw ellipses that match the criteria.

Arguments:

draw_style -- str

draw_text_style -- str

content -- str regex

Return: list of odf_shape

def get_draw_ellipse( self, position: int = 0, id: str | None = None, content: str | None = None) -> Element | None:
3065    def get_draw_ellipse(
3066        self,
3067        position: int = 0,
3068        id: str | None = None,  # noqa:A002
3069        content: str | None = None,
3070    ) -> Element | None:
3071        """Return the draw ellipse that matches the criteria.
3072
3073        Arguments:
3074
3075            position -- int
3076
3077            id -- str
3078
3079            content -- str regex
3080
3081        Return: odf_shape or None if not found
3082        """
3083        return self._filtered_element(
3084            "descendant::draw:ellipse", position, draw_id=id, content=content
3085        )

Return the draw ellipse that matches the criteria.

Arguments:

position -- int

id -- str

content -- str regex

Return: odf_shape or None if not found

def get_draw_connectors( self, draw_style: str | None = None, draw_text_style: str | None = None, content: str | None = None) -> list[Element]:
3089    def get_draw_connectors(
3090        self,
3091        draw_style: str | None = None,
3092        draw_text_style: str | None = None,
3093        content: str | None = None,
3094    ) -> list[Element]:
3095        """Return all the draw connectors that match the criteria.
3096
3097        Arguments:
3098
3099            draw_style -- str
3100
3101            draw_text_style -- str
3102
3103            content -- str regex
3104
3105        Return: list of odf_shape
3106        """
3107        return self._filtered_elements(
3108            "descendant::draw:connector",
3109            draw_style=draw_style,
3110            draw_text_style=draw_text_style,
3111            content=content,
3112        )

Return all the draw connectors that match the criteria.

Arguments:

draw_style -- str

draw_text_style -- str

content -- str regex

Return: list of odf_shape

def get_draw_connector( self, position: int = 0, id: str | None = None, content: str | None = None) -> Element | None:
3114    def get_draw_connector(
3115        self,
3116        position: int = 0,
3117        id: str | None = None,  # noqa:A002
3118        content: str | None = None,
3119    ) -> Element | None:
3120        """Return the draw connector that matches the criteria.
3121
3122        Arguments:
3123
3124            position -- int
3125
3126            id -- str
3127
3128            content -- str regex
3129
3130        Return: odf_shape or None if not found
3131        """
3132        return self._filtered_element(
3133            "descendant::draw:connector", position, draw_id=id, content=content
3134        )

Return the draw connector that matches the criteria.

Arguments:

position -- int

id -- str

content -- str regex

Return: odf_shape or None if not found

def get_orphan_draw_connectors(self) -> list[Element]:
3136    def get_orphan_draw_connectors(self) -> list[Element]:
3137        """Return a list of connectors which don't have any shape connected
3138        to them.
3139        """
3140        connectors = []
3141        for connector in self.get_draw_connectors():
3142            start_shape = connector.get_attribute("draw:start-shape")
3143            end_shape = connector.get_attribute("draw:end-shape")
3144            if start_shape is None and end_shape is None:
3145                connectors.append(connector)
3146        return connectors

Return a list of connectors which don't have any shape connected to them.

def get_tracked_changes(self) -> Element | None:
3150    def get_tracked_changes(self) -> Element | None:
3151        """Return the tracked-changes part in the text body."""
3152        return self.get_element("//text:tracked-changes")

Return the tracked-changes part in the text body.

def get_changes_ids(self) -> list[Element | Text]:
3154    def get_changes_ids(self) -> list[Element | Text]:
3155        """Return a list of ids that refers to a change region in the tracked
3156        changes list.
3157        """
3158        # Insertion changes
3159        xpath_query = "descendant::text:change-start/@text:change-id"
3160        # Deletion changes
3161        xpath_query += " | descendant::text:change/@text:change-id"
3162        return self.xpath(xpath_query)

Return a list of ids that refers to a change region in the tracked changes list.

def get_text_change_deletions(self) -> list[Element]:
3164    def get_text_change_deletions(self) -> list[Element]:
3165        """Return all the text changes of deletion kind: the tags text:change.
3166        Consider using : get_text_changes()
3167
3168        Return: list of Element
3169        """
3170        return self._filtered_elements("descendant::text:text:change")

Return all the text changes of deletion kind: the tags text:change. Consider using : get_text_changes()

Return: list of Element

def get_text_change_deletion( self, position: int = 0, idx: str | None = None) -> Element | None:
3172    def get_text_change_deletion(
3173        self,
3174        position: int = 0,
3175        idx: str | None = None,
3176    ) -> Element | None:
3177        """Return the text change of deletion kind that matches the criteria.
3178        Search only for the tags text:change.
3179        Consider using : get_text_change()
3180
3181        Arguments:
3182
3183            position -- int
3184
3185            idx -- str
3186
3187        Return: Element or None if not found
3188        """
3189        return self._filtered_element(
3190            "descendant::text:change", position, change_id=idx
3191        )

Return the text change of deletion kind that matches the criteria. Search only for the tags text:change. Consider using : get_text_change()

Arguments:

position -- int

idx -- str

Return: Element or None if not found

def get_text_change_starts(self) -> list[Element]:
3193    def get_text_change_starts(self) -> list[Element]:
3194        """Return all the text change-start. Search only for the tags
3195        text:change-start.
3196        Consider using : get_text_changes()
3197
3198        Return: list of Element
3199        """
3200        return self._filtered_elements("descendant::text:change-start")

Return all the text change-start. Search only for the tags text:change-start. Consider using : get_text_changes()

Return: list of Element

def get_text_change_start( self, position: int = 0, idx: str | None = None) -> Element | None:
3202    def get_text_change_start(
3203        self,
3204        position: int = 0,
3205        idx: str | None = None,
3206    ) -> Element | None:
3207        """Return the text change-start that matches the criteria. Search
3208        only the tags text:change-start.
3209        Consider using : get_text_change()
3210
3211        Arguments:
3212
3213            position -- int
3214
3215            idx -- str
3216
3217        Return: Element or None if not found
3218        """
3219        return self._filtered_element(
3220            "descendant::text:change-start", position, change_id=idx
3221        )

Return the text change-start that matches the criteria. Search only the tags text:change-start. Consider using : get_text_change()

Arguments:

position -- int

idx -- str

Return: Element or None if not found

def get_text_change_ends(self) -> list[Element]:
3223    def get_text_change_ends(self) -> list[Element]:
3224        """Return all the text change-end. Search only the tags
3225        text:change-end.
3226        Consider using : get_text_changes()
3227
3228        Return: list of Element
3229        """
3230        return self._filtered_elements("descendant::text:change-end")

Return all the text change-end. Search only the tags text:change-end. Consider using : get_text_changes()

Return: list of Element

def get_text_change_end( self, position: int = 0, idx: str | None = None) -> Element | None:
3232    def get_text_change_end(
3233        self,
3234        position: int = 0,
3235        idx: str | None = None,
3236    ) -> Element | None:
3237        """Return the text change-end that matches the criteria. Search only
3238        the tags text:change-end.
3239        Consider using : get_text_change()
3240
3241        Arguments:
3242
3243            position -- int
3244
3245            idx -- str
3246
3247        Return: Element or None if not found
3248        """
3249        return self._filtered_element(
3250            "descendant::text:change-end", position, change_id=idx
3251        )

Return the text change-end that matches the criteria. Search only the tags text:change-end. Consider using : get_text_change()

Arguments:

position -- int

idx -- str

Return: Element or None if not found

def get_text_changes(self) -> list[Element]:
3253    def get_text_changes(self) -> list[Element]:
3254        """Return all the text changes, either single deletion
3255        (text:change) or start of range of changes (text:change-start).
3256
3257        Return: list of Element
3258        """
3259        request = "descendant::text:change-start | descendant::text:change"
3260        return self._filtered_elements(request)

Return all the text changes, either single deletion (text:change) or start of range of changes (text:change-start).

Return: list of Element

def get_text_change( self, position: int = 0, idx: str | None = None) -> Element | None:
3262    def get_text_change(
3263        self,
3264        position: int = 0,
3265        idx: str | None = None,
3266    ) -> Element | None:
3267        """Return the text change that matches the criteria. Either single
3268        deletion (text:change) or start of range of changes (text:change-start).
3269        position : index of the element to retrieve if several matches, default
3270        is 0.
3271        idx : change-id of the element.
3272
3273        Arguments:
3274
3275            position -- int
3276
3277            idx -- str
3278
3279        Return: Element or None if not found
3280        """
3281        if idx:
3282            request = (
3283                f'descendant::text:change-start[@text:change-id="{idx}"] '
3284                f'| descendant::text:change[@text:change-id="{idx}"]'
3285            )
3286            return self._filtered_element(request, 0)
3287        request = "descendant::text:change-start | descendant::text:change"
3288        return self._filtered_element(request, position)

Return the text change that matches the criteria. Either single deletion (text:change) or start of range of changes (text:change-start). position : index of the element to retrieve if several matches, default is 0. idx : change-id of the element.

Arguments:

position -- int

idx -- str

Return: Element or None if not found

def get_tocs(self) -> list[Element]:
3292    def get_tocs(self) -> list[Element]:
3293        """Return all the tables of contents.
3294
3295        Return: list of odf_toc
3296        """
3297        return self._filtered_elements("text:table-of-content")

Return all the tables of contents.

Return: list of odf_toc

def get_toc( self, position: int = 0, content: str | None = None) -> Element | None:
3299    def get_toc(
3300        self,
3301        position: int = 0,
3302        content: str | None = None,
3303    ) -> Element | None:
3304        """Return the table of contents that matches the criteria.
3305
3306        Arguments:
3307
3308            position -- int
3309
3310            content -- str regex
3311
3312        Return: odf_toc or None if not found
3313        """
3314        return self._filtered_element(
3315            "text:table-of-content", position, content=content
3316        )

Return the table of contents that matches the criteria.

Arguments:

position -- int

content -- str regex

Return: odf_toc or None if not found

def get_styles(self, family: str | None = None) -> list[Element]:
3338    def get_styles(self, family: str | None = None) -> list[Element]:
3339        # Both common and default styles
3340        tagname = self._get_style_tagname(family)
3341        return self._filtered_elements(tagname, family=family)
def get_style( self, family: str, name_or_element: str | Element | None = None, display_name: str | None = None) -> Element | None:
3343    def get_style(
3344        self,
3345        family: str,
3346        name_or_element: str | Element | None = None,
3347        display_name: str | None = None,
3348    ) -> Element | None:
3349        """Return the style uniquely identified by the family/name pair. If
3350        the argument is already a style object, it will return it.
3351
3352        If the name is not the internal name but the name you gave in the
3353        desktop application, use display_name instead.
3354
3355        Arguments:
3356
3357            family -- 'paragraph', 'text', 'graphic', 'table', 'list',
3358                      'number'
3359
3360            name_or_element -- str or Style
3361
3362            display_name -- str
3363
3364        Return: odf_style or None if not found
3365        """
3366        if isinstance(name_or_element, Element):
3367            name = self.get_attribute("style:name")
3368            if name is not None:
3369                return name_or_element
3370            else:
3371                raise ValueError(f"Not a odf_style ? {name_or_element!r}")
3372        style_name = name_or_element
3373        is_default = not (style_name or display_name)
3374        tagname = self._get_style_tagname(family, is_default=is_default)
3375        # famattr became None if no "style:family" attribute
3376        if family:
3377            return self._filtered_element(
3378                tagname,
3379                0,
3380                style_name=style_name,
3381                display_name=display_name,
3382                family=family,
3383            )
3384        else:
3385            return self._filtered_element(
3386                tagname,
3387                0,
3388                draw_name=style_name or display_name,
3389                family=family,
3390            )

Return the style uniquely identified by the family/name pair. If the argument is already a style object, it will return it.

If the name is not the internal name but the name you gave in the desktop application, use display_name instead.

Arguments:

family -- 'paragraph', 'text', 'graphic', 'table', 'list',
          'number'

name_or_element -- str or Style

display_name -- str

Return: odf_style or None if not found

class ElementTyped(odfdo.Element):
 35class ElementTyped(Element):
 36    def set_value_and_type(  # noqa: C901
 37        self,
 38        value: Any,
 39        value_type: str | None = None,
 40        text: str | None = None,
 41        currency: str | None = None,
 42    ) -> str | None:
 43        # Remove possible previous value and type
 44        for name in (
 45            "office:value-type",
 46            "office:boolean-value",
 47            "office:value",
 48            "office:date-value",
 49            "office:string-value",
 50            "office:time-value",
 51            "table:formula",
 52            "office:currency",
 53            "calcext:value-type",
 54            "loext:value-type",
 55        ):
 56            with contextlib.suppress(KeyError):
 57                self.del_attribute(name)
 58        if isinstance(value, bytes):
 59            value = bytes_to_str(value)
 60        if isinstance(value_type, bytes):
 61            value_type = bytes_to_str(value_type)
 62        if isinstance(text, bytes):
 63            text = bytes_to_str(text)
 64        if isinstance(currency, bytes):
 65            currency = bytes_to_str(currency)
 66        if value is None:
 67            self._erase_text_content()
 68            return text
 69        if isinstance(value, bool):
 70            if value_type is None:
 71                value_type = "boolean"
 72            if text is None:
 73                text = "true" if value else "false"
 74            value = Boolean.encode(value)
 75        elif isinstance(value, (int, float, Decimal)):
 76            if value_type == "percentage":
 77                text = "%d %%" % int(value * 100)
 78            if value_type is None:
 79                value_type = "float"
 80            if text is None:
 81                text = str(value)
 82            value = str(value)
 83        elif isinstance(value, datetime):
 84            if value_type is None:
 85                value_type = "date"
 86            if text is None:
 87                text = str(DateTime.encode(value))
 88            value = DateTime.encode(value)
 89        elif isinstance(value, date):
 90            if value_type is None:
 91                value_type = "date"
 92            if text is None:
 93                text = str(Date.encode(value))
 94            value = Date.encode(value)
 95        elif isinstance(value, str):
 96            if value_type is None:
 97                value_type = "string"
 98            if text is None:
 99                text = value
100        elif isinstance(value, timedelta):
101            if value_type is None:
102                value_type = "time"
103            if text is None:
104                text = str(Duration.encode(value))
105            value = Duration.encode(value)
106        elif value is not None:
107            raise TypeError(f"Type unknown: '{value!r}'")
108
109        if value_type is not None:
110            self.set_attribute("office:value-type", value_type)
111            self.set_attribute("calcext:value-type", value_type)
112        if value_type == "boolean":
113            self.set_attribute("office:boolean-value", value)
114        elif value_type == "currency":
115            self.set_attribute("office:value", value)
116            self.set_attribute("office:currency", currency)
117        elif value_type == "date":
118            self.set_attribute("office:date-value", value)
119        elif value_type in ("float", "percentage"):
120            self.set_attribute("office:value", value)
121            self.set_attribute("calcext:value", value)
122        elif value_type == "string":
123            self.set_attribute("office:string-value", value)
124        elif value_type == "time":
125            self.set_attribute("office:time-value", value)
126
127        return text
128
129    def _get_typed_value(  # noqa: C901
130        self,
131        value_type: str | None = None,
132        try_get_text: bool = True,
133    ) -> tuple[Any, str | None]:
134        """Return Python typed value.
135
136        Only for "with office:value-type" elements, not for meta fields."""
137        value: Decimal | str | bool | None = None
138        if value_type is None:
139            read_value_type = self.get_attribute("office:value-type")
140            if isinstance(read_value_type, bool):
141                raise TypeError(
142                    f'Wrong type for "office:value-type": {type(read_value_type)}'
143                )
144            value_type = read_value_type
145        # value_type = to_str(value_type)
146        if value_type == "boolean":
147            value = self.get_attribute("office:boolean-value")
148            return (value, value_type)
149        if value_type in {"float", "percentage", "currency"}:
150            read_number = self.get_attribute("office:value")
151            if not isinstance(read_number, (Decimal, str)):
152                raise TypeError(f'Wrong type for "office:value": {type(read_number)}')
153            value = Decimal(read_number)
154            # Return 3 instead of 3.0 if possible
155            if int(value) == value:
156                return (int(value), value_type)
157            return (value, value_type)
158        if value_type == "date":
159            read_attribute = self.get_attribute("office:date-value")
160            if not isinstance(read_attribute, str):
161                raise TypeError(
162                    f'Wrong type for "office:date-value": {type(read_attribute)}'
163                )
164            if "T" in read_attribute:
165                return (DateTime.decode(read_attribute), value_type)
166            return (Date.decode(read_attribute), value_type)
167        if value_type == "string":
168            value = self.get_attribute("office:string-value")
169            if value is not None:
170                return (str(value), value_type)
171            if try_get_text:
172                list_value = [
173                    para.text_recursive for para in self.get_elements("text:p")
174                ]
175                if list_value:
176                    return ("\n".join(list_value), value_type)
177            return (None, value_type)
178        if value_type == "time":
179            read_value = self.get_attribute("office:time-value")
180            if not isinstance(read_value, str):
181                raise TypeError(
182                    f'Wrong type for "office:time-value": {type(read_value)}'
183                )
184            time_value = Duration.decode(read_value)
185            return (time_value, value_type)
186        if value_type is None:
187            return (None, None)
188        raise ValueError(f'Unexpected value type: "{value_type}"')
189
190    def get_value(
191        self,
192        value_type: str | None = None,
193        try_get_text: bool = True,
194        get_type: bool = False,
195    ) -> Any | tuple[Any, str]:
196        """Return Python typed value.
197
198        Only for "with office:value-type" elements, not for meta fields."""
199        if get_type:
200            return self._get_typed_value(
201                value_type=value_type,
202                try_get_text=try_get_text,
203            )
204        return self._get_typed_value(
205            value_type=value_type,
206            try_get_text=try_get_text,
207        )[0]

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

def set_value_and_type( self, value: Any, value_type: str | None = None, text: str | None = None, currency: str | None = None) -> str | None:
 36    def set_value_and_type(  # noqa: C901
 37        self,
 38        value: Any,
 39        value_type: str | None = None,
 40        text: str | None = None,
 41        currency: str | None = None,
 42    ) -> str | None:
 43        # Remove possible previous value and type
 44        for name in (
 45            "office:value-type",
 46            "office:boolean-value",
 47            "office:value",
 48            "office:date-value",
 49            "office:string-value",
 50            "office:time-value",
 51            "table:formula",
 52            "office:currency",
 53            "calcext:value-type",
 54            "loext:value-type",
 55        ):
 56            with contextlib.suppress(KeyError):
 57                self.del_attribute(name)
 58        if isinstance(value, bytes):
 59            value = bytes_to_str(value)
 60        if isinstance(value_type, bytes):
 61            value_type = bytes_to_str(value_type)
 62        if isinstance(text, bytes):
 63            text = bytes_to_str(text)
 64        if isinstance(currency, bytes):
 65            currency = bytes_to_str(currency)
 66        if value is None:
 67            self._erase_text_content()
 68            return text
 69        if isinstance(value, bool):
 70            if value_type is None:
 71                value_type = "boolean"
 72            if text is None:
 73                text = "true" if value else "false"
 74            value = Boolean.encode(value)
 75        elif isinstance(value, (int, float, Decimal)):
 76            if value_type == "percentage":
 77                text = "%d %%" % int(value * 100)
 78            if value_type is None:
 79                value_type = "float"
 80            if text is None:
 81                text = str(value)
 82            value = str(value)
 83        elif isinstance(value, datetime):
 84            if value_type is None:
 85                value_type = "date"
 86            if text is None:
 87                text = str(DateTime.encode(value))
 88            value = DateTime.encode(value)
 89        elif isinstance(value, date):
 90            if value_type is None:
 91                value_type = "date"
 92            if text is None:
 93                text = str(Date.encode(value))
 94            value = Date.encode(value)
 95        elif isinstance(value, str):
 96            if value_type is None:
 97                value_type = "string"
 98            if text is None:
 99                text = value
100        elif isinstance(value, timedelta):
101            if value_type is None:
102                value_type = "time"
103            if text is None:
104                text = str(Duration.encode(value))
105            value = Duration.encode(value)
106        elif value is not None:
107            raise TypeError(f"Type unknown: '{value!r}'")
108
109        if value_type is not None:
110            self.set_attribute("office:value-type", value_type)
111            self.set_attribute("calcext:value-type", value_type)
112        if value_type == "boolean":
113            self.set_attribute("office:boolean-value", value)
114        elif value_type == "currency":
115            self.set_attribute("office:value", value)
116            self.set_attribute("office:currency", currency)
117        elif value_type == "date":
118            self.set_attribute("office:date-value", value)
119        elif value_type in ("float", "percentage"):
120            self.set_attribute("office:value", value)
121            self.set_attribute("calcext:value", value)
122        elif value_type == "string":
123            self.set_attribute("office:string-value", value)
124        elif value_type == "time":
125            self.set_attribute("office:time-value", value)
126
127        return text
def get_value( self, value_type: str | None = None, try_get_text: bool = True, get_type: bool = False) -> typing.Any | tuple[typing.Any, str]:
190    def get_value(
191        self,
192        value_type: str | None = None,
193        try_get_text: bool = True,
194        get_type: bool = False,
195    ) -> Any | tuple[Any, str]:
196        """Return Python typed value.
197
198        Only for "with office:value-type" elements, not for meta fields."""
199        if get_type:
200            return self._get_typed_value(
201                value_type=value_type,
202                try_get_text=try_get_text,
203            )
204        return self._get_typed_value(
205            value_type=value_type,
206            try_get_text=try_get_text,
207        )[0]

Return Python typed value.

Only for "with office:value-type" elements, not for meta fields.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class EllipseShape(odfdo.shapes.ShapeBase):
194class EllipseShape(ShapeBase):
195    """Create a ellipse shape.
196
197    Arguments:
198
199        style -- str
200
201        text_style -- str
202
203        draw_id -- str
204
205        layer -- str
206
207        position -- (str, str)
208
209        size -- (str, str)
210
211    """
212
213    _tag = "draw:ellipse"
214    _properties: tuple[PropDef, ...] = ()
215
216    def __init__(
217        self,
218        style: str | None = None,
219        text_style: str | None = None,
220        draw_id: str | None = None,
221        layer: str | None = None,
222        position: tuple | None = None,
223        size: tuple | None = None,
224        **kwargs: Any,
225    ) -> None:
226        kwargs.update(
227            {
228                "style": style,
229                "text_style": text_style,
230                "draw_id": draw_id,
231                "layer": layer,
232                "size": size,
233                "position": position,
234            }
235        )
236        super().__init__(**kwargs)

Create a ellipse shape.

Arguments:

style -- str

text_style -- str

draw_id -- str

layer -- str

position -- (str, str)

size -- (str, str)
EllipseShape( style: str | None = None, text_style: str | None = None, draw_id: str | None = None, layer: str | None = None, position: tuple | None = None, size: tuple | None = None, **kwargs: Any)
216    def __init__(
217        self,
218        style: str | None = None,
219        text_style: str | None = None,
220        draw_id: str | None = None,
221        layer: str | None = None,
222        position: tuple | None = None,
223        size: tuple | None = None,
224        **kwargs: Any,
225    ) -> None:
226        kwargs.update(
227            {
228                "style": style,
229                "text_style": text_style,
230                "draw_id": draw_id,
231                "layer": layer,
232                "size": size,
233                "position": position,
234            }
235        )
236        super().__init__(**kwargs)
Inherited Members
odfdo.shapes.ShapeBase
get_formatted_text
draw_id
layer
width
height
pos_x
pos_y
presentation_class
style
text_style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.SizeMix
size
odfdo.frame.PosMix
position
FIRST_CHILD = 0
class Frame(odfdo.Element, odfdo.frame.AnchorMix, odfdo.frame.PosMix, odfdo.frame.ZMix, odfdo.frame.SizeMix):
168class Frame(Element, AnchorMix, PosMix, ZMix, SizeMix):
169    """ODF Frame "draw:frame"
170
171    Frames are not useful by themselves. You should consider calling
172    Frame.image_frame() or Frame.text_frame directly.
173    """
174
175    _tag = "draw:frame"
176    _properties = (
177        PropDef("name", "draw:name"),
178        PropDef("draw_id", "draw:id"),
179        PropDef("width", "svg:width"),
180        PropDef("height", "svg:height"),
181        PropDef("style", "draw:style-name"),
182        PropDef("pos_x", "svg:x"),
183        PropDef("pos_y", "svg:y"),
184        PropDef("presentation_class", "presentation:class"),
185        PropDef("layer", "draw:layer"),
186        PropDef("presentation_style", "presentation:style-name"),
187    )
188
189    def __init__(  # noqa:  C901
190        self,
191        name: str | None = None,
192        draw_id: str | None = None,
193        style: str | None = None,
194        position: tuple | None = None,
195        size: tuple = ("1cm", "1cm"),
196        z_index: int = 0,
197        presentation_class: str | None = None,
198        anchor_type: str | None = None,
199        anchor_page: int | None = None,
200        layer: str | None = None,
201        presentation_style: str | None = None,
202        **kwargs: Any,
203    ) -> None:
204        """Create a frame element of the given size. Position is relative to the
205        context the frame is inserted in. If positioned by page, give the page
206        number and the x, y position.
207
208        Size is a (width, height) tuple and position is a (left, top) tuple; items
209        are strings including the unit, e.g. ('10cm', '15cm').
210
211        Frames are not useful by themselves. You should consider calling:
212            Frame.image_frame()
213        or
214            Frame.text_frame()
215
216
217        Arguments:
218
219            name -- str
220
221            draw_id -- str
222
223            style -- str
224
225            position -- (str, str)
226
227            size -- (str, str)
228
229            z_index -- int (default 0)
230
231            presentation_class -- str
232
233            anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'
234
235            anchor_page -- int, page number is anchor_type is 'page'
236
237            layer -- str
238
239            presentation_style -- str
240        """
241        super().__init__(**kwargs)
242        if self._do_init:
243            self.size = size
244            self.z_index = z_index
245            if name:
246                self.name = name
247            if draw_id is not None:
248                self.draw_id = draw_id
249            if style is not None:
250                self.style = style
251            if position is not None:
252                self.position = position
253            if presentation_class is not None:
254                self.presentation_class = presentation_class
255            if anchor_type:
256                self.anchor_type = anchor_type
257            if position and not anchor_type:
258                self.anchor_type = "paragraph"
259            if anchor_page is not None:
260                self.anchor_page = anchor_page
261            if layer is not None:
262                self.layer = layer
263            if presentation_style is not None:
264                self.presentation_style = presentation_style
265
266    @classmethod
267    def image_frame(
268        cls,
269        image: Element | str,
270        text: str | None = None,
271        name: str | None = None,
272        draw_id: str | None = None,
273        style: str | None = None,
274        position: tuple | None = None,
275        size: tuple = ("1cm", "1cm"),
276        z_index: int = 0,
277        presentation_class: str | None = None,
278        anchor_type: str | None = None,
279        anchor_page: int | None = None,
280        layer: str | None = None,
281        presentation_style: str | None = None,
282        **kwargs: Any,
283    ) -> Element:
284        """Create a ready-to-use image, since image must be embedded in a
285        frame.
286
287        The optionnal text will appear above the image.
288
289        Arguments:
290
291            image -- DrawImage or str, DrawImage element or URL of the image
292
293            text -- str, text for the image
294
295            See Frame() initialization for the other arguments
296
297        Return: Frame
298        """
299        frame = cls(
300            name=name,
301            draw_id=draw_id,
302            style=style,
303            position=position,
304            size=size,
305            z_index=z_index,
306            presentation_class=presentation_class,
307            anchor_type=anchor_type,
308            anchor_page=anchor_page,
309            layer=layer,
310            presentation_style=presentation_style,
311            **kwargs,
312        )
313        image_element = frame.set_image(image)
314        if text:
315            image_element.text_content = text
316        return frame
317
318    @classmethod
319    def text_frame(
320        cls,
321        text_or_element: Iterable[Element] | Element | str,
322        text_style: str | None = None,
323        name: str | None = None,
324        draw_id: str | None = None,
325        style: str | None = None,
326        position: tuple | None = None,
327        size: tuple = ("1cm", "1cm"),
328        z_index: int = 0,
329        presentation_class: str | None = None,
330        anchor_type: str | None = None,
331        anchor_page: int | None = None,
332        layer: str | None = None,
333        presentation_style: str | None = None,
334        **kwargs: Any,
335    ) -> Element:
336        """Create a ready-to-use text box, since text box must be embedded in
337        a frame.
338
339        The optionnal text will appear above the image.
340
341        Arguments:
342
343            text_or_element -- str or Element, or list of them, text content
344                               of the text box.
345
346            text_style -- str, name of the style for the text
347
348            See Frame() initialization for the other arguments
349
350        Return: Frame
351        """
352        frame = cls(
353            name=name,
354            draw_id=draw_id,
355            style=style,
356            position=position,
357            size=size,
358            z_index=z_index,
359            presentation_class=presentation_class,
360            anchor_type=anchor_type,
361            anchor_page=anchor_page,
362            layer=layer,
363            presentation_style=presentation_style,
364            **kwargs,
365        )
366        frame.set_text_box(text_or_element, text_style)
367        return frame
368
369    @property
370    def text_content(self) -> str:
371        text_box = self.get_element("draw:text-box")
372        if text_box is None:
373            return ""
374        return text_box.text_content
375
376    @text_content.setter
377    def text_content(self, text_or_element: Element | str) -> None:
378        text_box = self.get_element("draw:text-box")
379        if text_box is None:
380            text_box = Element.from_tag("draw:text-box")
381            self.append(text_box)
382        if isinstance(text_or_element, Element):
383            text_box.clear()
384            text_box.append(text_or_element)
385        else:
386            text_box.text_content = text_or_element
387
388    def get_image(
389        self,
390        position: int = 0,
391        name: str | None = None,
392        url: str | None = None,
393        content: str | None = None,
394    ) -> Element | None:
395        return self.get_element("draw:image")
396
397    def set_image(self, url_or_element: Element | str) -> Element:
398        image = self.get_image()
399        if image is None:
400            if isinstance(url_or_element, Element):
401                image = url_or_element
402                self.append(image)
403            else:
404                image = DrawImage(url_or_element)
405                self.append(image)
406        else:
407            if isinstance(url_or_element, Element):
408                image.delete()
409                image = url_or_element
410                self.append(image)
411            else:
412                image.set_url(url_or_element)  # type: ignore
413        return image
414
415    def get_text_box(self) -> Element | None:
416        return self.get_element("draw:text-box")
417
418    def set_text_box(
419        self,
420        text_or_element: Iterable[Element | str] | Element | str,
421        text_style: str | None = None,
422    ) -> Element:
423        text_box = self.get_text_box()
424        if text_box is None:
425            text_box = Element.from_tag("draw:text-box")
426            self.append(text_box)
427        else:
428            text_box.clear()
429        if isinstance(text_or_element, (Element, str)):
430            text_or_element_list: Iterable[Element | str] = [text_or_element]
431        else:
432            text_or_element_list = text_or_element
433        for item in text_or_element_list:
434            if isinstance(item, str):
435                text_box.append(Paragraph(item, style=text_style))
436            else:
437                text_box.append(item)
438        return text_box
439
440    @staticmethod
441    def _get_formatted_text_subresult(context: dict, element: Element) -> str:
442        str_list = ["  "]
443        for child in element.children:
444            str_list.append(child.get_formatted_text(context))
445        subresult = "".join(str_list)
446        subresult = subresult.replace("\n", "\n  ")
447        return subresult.rstrip(" ")
448
449    def get_formatted_text(  # noqa:  C901
450        self,
451        context: dict | None = None,
452    ) -> str:
453        if not context:
454            context = {}
455        result = []
456        for element in self.children:
457            tag = element.tag
458            if tag == "draw:image":
459                if context["rst_mode"]:
460                    filename = element.get_attribute("xlink:href")
461
462                    # Compute width and height
463                    width, height = self.size
464                    if width is not None:
465                        width = Unit(width)
466                        width = width.convert("px", DPI)
467                    if height is not None:
468                        height = Unit(height)
469                        height = height.convert("px", DPI)
470
471                    # Insert or not ?
472                    if context["no_img_level"]:
473                        context["img_counter"] += 1
474                        ref = f"|img{context['img_counter']}|"
475                        result.append(ref)
476                        context["images"].append((ref, filename, (width, height)))
477                    else:
478                        result.append(f"\n.. image:: {filename}\n")
479                        if width is not None:
480                            result.append(f"   :width: {width}\n")
481                        if height is not None:
482                            result.append(f"   :height: {height}\n")
483                else:
484                    result.append(f"[Image {element.get_attribute('xlink:href')}]\n")
485            elif tag == "draw:text-box":
486                result.append(self._get_formatted_text_subresult(context, element))
487            else:
488                result.append(element.get_formatted_text(context))
489        result.append("\n")
490        return "".join(result)

ODF Frame "draw:frame"

Frames are not useful by themselves. You should consider calling Frame.image_frame() or Frame.text_frame directly.

Frame( name: str | None = None, draw_id: str | None = None, style: str | None = None, position: tuple | None = None, size: tuple = ('1cm', '1cm'), z_index: int = 0, presentation_class: str | None = None, anchor_type: str | None = None, anchor_page: int | None = None, layer: str | None = None, presentation_style: str | None = None, **kwargs: Any)
189    def __init__(  # noqa:  C901
190        self,
191        name: str | None = None,
192        draw_id: str | None = None,
193        style: str | None = None,
194        position: tuple | None = None,
195        size: tuple = ("1cm", "1cm"),
196        z_index: int = 0,
197        presentation_class: str | None = None,
198        anchor_type: str | None = None,
199        anchor_page: int | None = None,
200        layer: str | None = None,
201        presentation_style: str | None = None,
202        **kwargs: Any,
203    ) -> None:
204        """Create a frame element of the given size. Position is relative to the
205        context the frame is inserted in. If positioned by page, give the page
206        number and the x, y position.
207
208        Size is a (width, height) tuple and position is a (left, top) tuple; items
209        are strings including the unit, e.g. ('10cm', '15cm').
210
211        Frames are not useful by themselves. You should consider calling:
212            Frame.image_frame()
213        or
214            Frame.text_frame()
215
216
217        Arguments:
218
219            name -- str
220
221            draw_id -- str
222
223            style -- str
224
225            position -- (str, str)
226
227            size -- (str, str)
228
229            z_index -- int (default 0)
230
231            presentation_class -- str
232
233            anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'
234
235            anchor_page -- int, page number is anchor_type is 'page'
236
237            layer -- str
238
239            presentation_style -- str
240        """
241        super().__init__(**kwargs)
242        if self._do_init:
243            self.size = size
244            self.z_index = z_index
245            if name:
246                self.name = name
247            if draw_id is not None:
248                self.draw_id = draw_id
249            if style is not None:
250                self.style = style
251            if position is not None:
252                self.position = position
253            if presentation_class is not None:
254                self.presentation_class = presentation_class
255            if anchor_type:
256                self.anchor_type = anchor_type
257            if position and not anchor_type:
258                self.anchor_type = "paragraph"
259            if anchor_page is not None:
260                self.anchor_page = anchor_page
261            if layer is not None:
262                self.layer = layer
263            if presentation_style is not None:
264                self.presentation_style = presentation_style

Create a frame element of the given size. Position is relative to the context the frame is inserted in. If positioned by page, give the page number and the x, y position.

Size is a (width, height) tuple and position is a (left, top) tuple; items are strings including the unit, e.g. ('10cm', '15cm').

Frames are not useful by themselves. You should consider calling: Frame.image_frame() or Frame.text_frame()

Arguments:

name -- str

draw_id -- str

style -- str

position -- (str, str)

size -- (str, str)

z_index -- int (default 0)

presentation_class -- str

anchor_type -- 'page', 'frame', 'paragraph', 'char' or 'as-char'

anchor_page -- int, page number is anchor_type is 'page'

layer -- str

presentation_style -- str
@classmethod
def image_frame( cls, image: Element | str, text: str | None = None, name: str | None = None, draw_id: str | None = None, style: str | None = None, position: tuple | None = None, size: tuple = ('1cm', '1cm'), z_index: int = 0, presentation_class: str | None = None, anchor_type: str | None = None, anchor_page: int | None = None, layer: str | None = None, presentation_style: str | None = None, **kwargs: Any) -> Element:
266    @classmethod
267    def image_frame(
268        cls,
269        image: Element | str,
270        text: str | None = None,
271        name: str | None = None,
272        draw_id: str | None = None,
273        style: str | None = None,
274        position: tuple | None = None,
275        size: tuple = ("1cm", "1cm"),
276        z_index: int = 0,
277        presentation_class: str | None = None,
278        anchor_type: str | None = None,
279        anchor_page: int | None = None,
280        layer: str | None = None,
281        presentation_style: str | None = None,
282        **kwargs: Any,
283    ) -> Element:
284        """Create a ready-to-use image, since image must be embedded in a
285        frame.
286
287        The optionnal text will appear above the image.
288
289        Arguments:
290
291            image -- DrawImage or str, DrawImage element or URL of the image
292
293            text -- str, text for the image
294
295            See Frame() initialization for the other arguments
296
297        Return: Frame
298        """
299        frame = cls(
300            name=name,
301            draw_id=draw_id,
302            style=style,
303            position=position,
304            size=size,
305            z_index=z_index,
306            presentation_class=presentation_class,
307            anchor_type=anchor_type,
308            anchor_page=anchor_page,
309            layer=layer,
310            presentation_style=presentation_style,
311            **kwargs,
312        )
313        image_element = frame.set_image(image)
314        if text:
315            image_element.text_content = text
316        return frame

Create a ready-to-use image, since image must be embedded in a frame.

The optionnal text will appear above the image.

Arguments:

image -- DrawImage or str, DrawImage element or URL of the image

text -- str, text for the image

See Frame() initialization for the other arguments

Return: Frame

@classmethod
def text_frame( cls, text_or_element: collections.abc.Iterable[Element] | Element | str, text_style: str | None = None, name: str | None = None, draw_id: str | None = None, style: str | None = None, position: tuple | None = None, size: tuple = ('1cm', '1cm'), z_index: int = 0, presentation_class: str | None = None, anchor_type: str | None = None, anchor_page: int | None = None, layer: str | None = None, presentation_style: str | None = None, **kwargs: Any) -> Element:
318    @classmethod
319    def text_frame(
320        cls,
321        text_or_element: Iterable[Element] | Element | str,
322        text_style: str | None = None,
323        name: str | None = None,
324        draw_id: str | None = None,
325        style: str | None = None,
326        position: tuple | None = None,
327        size: tuple = ("1cm", "1cm"),
328        z_index: int = 0,
329        presentation_class: str | None = None,
330        anchor_type: str | None = None,
331        anchor_page: int | None = None,
332        layer: str | None = None,
333        presentation_style: str | None = None,
334        **kwargs: Any,
335    ) -> Element:
336        """Create a ready-to-use text box, since text box must be embedded in
337        a frame.
338
339        The optionnal text will appear above the image.
340
341        Arguments:
342
343            text_or_element -- str or Element, or list of them, text content
344                               of the text box.
345
346            text_style -- str, name of the style for the text
347
348            See Frame() initialization for the other arguments
349
350        Return: Frame
351        """
352        frame = cls(
353            name=name,
354            draw_id=draw_id,
355            style=style,
356            position=position,
357            size=size,
358            z_index=z_index,
359            presentation_class=presentation_class,
360            anchor_type=anchor_type,
361            anchor_page=anchor_page,
362            layer=layer,
363            presentation_style=presentation_style,
364            **kwargs,
365        )
366        frame.set_text_box(text_or_element, text_style)
367        return frame

Create a ready-to-use text box, since text box must be embedded in a frame.

The optionnal text will appear above the image.

Arguments:

text_or_element -- str or Element, or list of them, text content
                   of the text box.

text_style -- str, name of the style for the text

See Frame() initialization for the other arguments

Return: Frame

text_content: str
369    @property
370    def text_content(self) -> str:
371        text_box = self.get_element("draw:text-box")
372        if text_box is None:
373            return ""
374        return text_box.text_content

Get / set the text of the embedded paragraph, including embeded annotations, cells...

Set create a paragraph if missing

def get_image( self, position: int = 0, name: str | None = None, url: str | None = None, content: str | None = None) -> Element | None:
388    def get_image(
389        self,
390        position: int = 0,
391        name: str | None = None,
392        url: str | None = None,
393        content: str | None = None,
394    ) -> Element | None:
395        return self.get_element("draw:image")

Return the image matching the criteria.

Arguments:

position -- int

name -- str

url -- str regex

content -- str regex

Return: Element or None if not found

def set_image( self, url_or_element: Element | str) -> Element:
397    def set_image(self, url_or_element: Element | str) -> Element:
398        image = self.get_image()
399        if image is None:
400            if isinstance(url_or_element, Element):
401                image = url_or_element
402                self.append(image)
403            else:
404                image = DrawImage(url_or_element)
405                self.append(image)
406        else:
407            if isinstance(url_or_element, Element):
408                image.delete()
409                image = url_or_element
410                self.append(image)
411            else:
412                image.set_url(url_or_element)  # type: ignore
413        return image
def get_text_box(self) -> Element | None:
415    def get_text_box(self) -> Element | None:
416        return self.get_element("draw:text-box")
def set_text_box( self, text_or_element: collections.abc.Iterable[Element | str] | Element | str, text_style: str | None = None) -> Element:
418    def set_text_box(
419        self,
420        text_or_element: Iterable[Element | str] | Element | str,
421        text_style: str | None = None,
422    ) -> Element:
423        text_box = self.get_text_box()
424        if text_box is None:
425            text_box = Element.from_tag("draw:text-box")
426            self.append(text_box)
427        else:
428            text_box.clear()
429        if isinstance(text_or_element, (Element, str)):
430            text_or_element_list: Iterable[Element | str] = [text_or_element]
431        else:
432            text_or_element_list = text_or_element
433        for item in text_or_element_list:
434            if isinstance(item, str):
435                text_box.append(Paragraph(item, style=text_style))
436            else:
437                text_box.append(item)
438        return text_box
def get_formatted_text(self, context: dict | None = None) -> str:
449    def get_formatted_text(  # noqa:  C901
450        self,
451        context: dict | None = None,
452    ) -> str:
453        if not context:
454            context = {}
455        result = []
456        for element in self.children:
457            tag = element.tag
458            if tag == "draw:image":
459                if context["rst_mode"]:
460                    filename = element.get_attribute("xlink:href")
461
462                    # Compute width and height
463                    width, height = self.size
464                    if width is not None:
465                        width = Unit(width)
466                        width = width.convert("px", DPI)
467                    if height is not None:
468                        height = Unit(height)
469                        height = height.convert("px", DPI)
470
471                    # Insert or not ?
472                    if context["no_img_level"]:
473                        context["img_counter"] += 1
474                        ref = f"|img{context['img_counter']}|"
475                        result.append(ref)
476                        context["images"].append((ref, filename, (width, height)))
477                    else:
478                        result.append(f"\n.. image:: {filename}\n")
479                        if width is not None:
480                            result.append(f"   :width: {width}\n")
481                        if height is not None:
482                            result.append(f"   :height: {height}\n")
483                else:
484                    result.append(f"[Image {element.get_attribute('xlink:href')}]\n")
485            elif tag == "draw:text-box":
486                result.append(self._get_formatted_text_subresult(context, element))
487            else:
488                result.append(element.get_formatted_text(context))
489        result.append("\n")
490        return "".join(result)

This function should return a beautiful version of the text.

name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
draw_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
width: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
height: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
pos_x: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
pos_y: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
presentation_class: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
layer: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
presentation_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.AnchorMix
ANCHOR_VALUE_CHOICE
anchor_type
anchor_page
odfdo.frame.PosMix
position
odfdo.frame.ZMix
z_index
odfdo.frame.SizeMix
size
class HeaderRows(odfdo.Element):
32class HeaderRows(Element):
33    _tag = "table:table-header-rows"
34    _caching = True

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class IndexTitle(odfdo.Element):
40class IndexTitle(Element):
41    """The "text:index-title" element contains the title of an index.
42
43    The element has the following attributes:
44    text:name, text:protected, text:protection-key,
45    text:protection-key-digest-algorithm, text:style-name, xml:id.
46
47    The actual title is stored in a child element
48    """
49
50    _tag = "text:index-title"
51    _properties = (
52        PropDef("name", "text:name"),
53        PropDef("style", "text:style-name"),
54        PropDef("xml_id", "xml:id"),
55        PropDef("protected", "text:protected"),
56        PropDef("protection_key", "text:protection-key"),
57        PropDef(
58            "protection_key_digest_algorithm", "text:protection-key-digest-algorithm"
59        ),
60    )
61
62    def __init__(
63        self,
64        name: str | None = None,
65        style: str | None = None,
66        title_text: str | None = None,
67        title_text_style: str | None = None,
68        xml_id: str | None = None,
69        **kwargs: Any,
70    ) -> None:
71        super().__init__(**kwargs)
72        if self._do_init:
73            if name:
74                self.name = name
75            if style:
76                self.style = style
77            if xml_id:
78                self.xml_id = xml_id
79            if title_text:
80                self.set_title_text(title_text, title_text_style)
81
82    def set_title_text(
83        self,
84        title_text: str,
85        title_text_style: str | None = None,
86    ) -> None:
87        title = Paragraph(title_text, style=title_text_style)
88        self.append(title)

The "text:index-title" element contains the title of an index.

The element has the following attributes: text:name, text:protected, text:protection-key, text:protection-key-digest-algorithm, text:style-name, xml:id.

The actual title is stored in a child element

IndexTitle( name: str | None = None, style: str | None = None, title_text: str | None = None, title_text_style: str | None = None, xml_id: str | None = None, **kwargs: Any)
62    def __init__(
63        self,
64        name: str | None = None,
65        style: str | None = None,
66        title_text: str | None = None,
67        title_text_style: str | None = None,
68        xml_id: str | None = None,
69        **kwargs: Any,
70    ) -> None:
71        super().__init__(**kwargs)
72        if self._do_init:
73            if name:
74                self.name = name
75            if style:
76                self.style = style
77            if xml_id:
78                self.xml_id = xml_id
79            if title_text:
80                self.set_title_text(title_text, title_text_style)
def set_title_text(self, title_text: str, title_text_style: str | None = None) -> None:
82    def set_title_text(
83        self,
84        title_text: str,
85        title_text_style: str | None = None,
86    ) -> None:
87        title = Paragraph(title_text, style=title_text_style)
88        self.append(title)
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
xml_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
protected: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
protection_key: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
protection_key_digest_algorithm: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class IndexTitleTemplate(odfdo.Element):
447class IndexTitleTemplate(Element):
448    """ODF "text:index-title-template"
449
450    Arguments:
451
452        style -- str
453    """
454
455    _tag = "text:index-title-template"
456    _properties = (PropDef("style", "text:style-name"),)
457
458    def __init__(self, style: str | None = None, **kwargs: Any) -> None:
459        super().__init__(**kwargs)
460        if self._do_init and style:
461            self.style = style

ODF "text:index-title-template"

Arguments:

style -- str
IndexTitleTemplate(style: str | None = None, **kwargs: Any)
458    def __init__(self, style: str | None = None, **kwargs: Any) -> None:
459        super().__init__(**kwargs)
460        if self._do_init and style:
461            self.style = style
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
LAST_CHILD = 1
class LineBreak(odfdo.Element):
223class LineBreak(Element):
224    """This element represents a line break "text:line-break" """
225
226    _tag = "text:line-break"
227
228    def __init__(self, **kwargs: Any) -> None:
229        super().__init__(**kwargs)

This element represents a line break "text:line-break"

LineBreak(**kwargs: Any)
228    def __init__(self, **kwargs: Any) -> None:
229        super().__init__(**kwargs)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class LineShape(odfdo.shapes.ShapeBase):
 89class LineShape(ShapeBase):
 90    """Create a line shape.
 91
 92    Arguments:
 93
 94        style -- str
 95
 96        text_style -- str
 97
 98        draw_id -- str
 99
100        layer -- str
101
102        p1 -- (str, str)
103
104        p2 -- (str, str)
105    """
106
107    _tag = "draw:line"
108    _properties: tuple[PropDef, ...] = (
109        PropDef("x1", "svg:x1"),
110        PropDef("y1", "svg:y1"),
111        PropDef("x2", "svg:x2"),
112        PropDef("y2", "svg:y2"),
113    )
114
115    def __init__(
116        self,
117        style: str | None = None,
118        text_style: str | None = None,
119        draw_id: str | None = None,
120        layer: str | None = None,
121        p1: tuple | None = None,
122        p2: tuple | None = None,
123        **kwargs: Any,
124    ) -> None:
125        kwargs.update(
126            {
127                "style": style,
128                "text_style": text_style,
129                "draw_id": draw_id,
130                "layer": layer,
131            }
132        )
133        super().__init__(**kwargs)
134        if self._do_init:
135            if p1:
136                self.x1 = p1[0]
137                self.y1 = p1[1]
138            if p2:
139                self.x2 = p2[0]
140                self.y2 = p2[1]

Create a line shape.

Arguments:

style -- str

text_style -- str

draw_id -- str

layer -- str

p1 -- (str, str)

p2 -- (str, str)
LineShape( style: str | None = None, text_style: str | None = None, draw_id: str | None = None, layer: str | None = None, p1: tuple | None = None, p2: tuple | None = None, **kwargs: Any)
115    def __init__(
116        self,
117        style: str | None = None,
118        text_style: str | None = None,
119        draw_id: str | None = None,
120        layer: str | None = None,
121        p1: tuple | None = None,
122        p2: tuple | None = None,
123        **kwargs: Any,
124    ) -> None:
125        kwargs.update(
126            {
127                "style": style,
128                "text_style": text_style,
129                "draw_id": draw_id,
130                "layer": layer,
131            }
132        )
133        super().__init__(**kwargs)
134        if self._do_init:
135            if p1:
136                self.x1 = p1[0]
137                self.y1 = p1[1]
138            if p2:
139                self.x2 = p2[0]
140                self.y2 = p2[1]
x1: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
y1: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
x2: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
y2: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
odfdo.shapes.ShapeBase
get_formatted_text
draw_id
layer
width
height
pos_x
pos_y
presentation_class
style
text_style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.SizeMix
size
odfdo.frame.PosMix
position
class List(odfdo.Element):
 68class List(Element):
 69    """ODF List "text:list"."""
 70
 71    _tag = "text:list"
 72    _properties = (PropDef("style", "text:style-name"),)
 73
 74    def __init__(
 75        self,
 76        list_content: str | Element | Iterable[str | Element] | None = None,
 77        style: str | None = None,
 78        **kwargs: Any,
 79    ) -> None:
 80        """Create a list element, optionaly loading the list by a list of
 81        item (str or elements).
 82
 83        The list_content argument is just a shortcut for the most common case.
 84        To create more complex lists, first create an empty list, and fill it
 85        afterwards.
 86
 87        Arguments:
 88
 89            list_content -- str or Element, or a list of str or Element
 90
 91            style -- str
 92        """
 93        super().__init__(**kwargs)
 94        if self._do_init:
 95            if list_content:
 96                if isinstance(list_content, (Element, str)):
 97                    self.append(ListItem(list_content))
 98                elif hasattr(list_content, "__iter__"):
 99                    for item in list_content:
100                        self.append(ListItem(item))
101            if style is not None:
102                self.style = style
103
104    def get_items(self, content: str | None = None) -> list[Element]:
105        """Return all the list items that match the criteria.
106
107        Arguments:
108
109            style -- str
110
111            content -- str regex
112
113        Return: list of Element
114        """
115        return self._filtered_elements("text:list-item", content=content)
116
117    def get_item(
118        self,
119        position: int = 0,
120        content: str | None = None,
121    ) -> Element | None:
122        """Return the list item that matches the criteria. In nested lists,
123        return the list item that really contains that content.
124
125        Arguments:
126
127            position -- int
128
129            content -- str regex
130
131        Return: Element or None if not found
132        """
133        # Custom implementation because of nested lists
134        if content:
135            # Don't search recursively but on the very own paragraph(s) of
136            # each list item
137            for paragraph in self.get_elements("descendant::text:p"):
138                if paragraph.match(content):
139                    return paragraph.get_element("parent::text:list-item")
140            return None
141        return self._filtered_element("text:list-item", position)
142
143    def set_list_header(
144        self,
145        text_or_element: str | Element | Iterable[str | Element],
146    ) -> None:
147        if isinstance(text_or_element, (str, Element)):
148            actual_list: list[str | Element] | tuple = [text_or_element]
149        elif isinstance(text_or_element, (list, tuple)):
150            actual_list = text_or_element
151        else:
152            raise TypeError
153        # Remove existing header
154        for element in self.get_elements("text:p"):
155            self.delete(element)
156        for paragraph in reversed(actual_list):
157            if isinstance(paragraph, str):
158                paragraph = Paragraph(paragraph)
159            self.insert(paragraph, FIRST_CHILD)
160
161    def insert_item(
162        self,
163        item: ListItem | str | Element | None,
164        position: int | None = None,
165        before: Element | None = None,
166        after: Element | None = None,
167    ) -> None:
168        if not isinstance(item, ListItem):
169            item = ListItem(item)
170        if before is not None:
171            before.insert(item, xmlposition=PREV_SIBLING)
172        elif after is not None:
173            after.insert(item, xmlposition=NEXT_SIBLING)
174        elif position is not None:
175            self.insert(item, position=position)
176        else:
177            raise ValueError("Position must be defined")
178
179    def append_item(
180        self,
181        item: ListItem | str | Element | None,
182    ) -> None:
183        if not isinstance(item, ListItem):
184            item = ListItem(item)
185        self.append(item)
186
187    def get_formatted_text(self, context: dict | None = None) -> str:
188        if context is None:
189            context = {}
190        rst_mode = context["rst_mode"]
191        result = []
192        if rst_mode:
193            result.append("\n")
194        for list_item in self.get_elements("text:list-item"):
195            textbuf = []
196            for child in list_item.children:
197                text = child.get_formatted_text(context)
198                tag = child.tag
199                if tag == "text:h":
200                    # A title in a list is a bug
201                    return text
202                if tag == "text:list" and not text.lstrip().startswith("-"):
203                    # If the list didn't indent, don't either
204                    # (inner title)
205                    return text
206                textbuf.append(text)
207            text_sum = "".join(textbuf)
208            text_sum = text_sum.strip("\n")
209            # Indent the text
210            text_sum = text_sum.replace("\n", "\n  ")
211            text_sum = f"- {text_sum}\n"
212            result.append(text_sum)
213        if rst_mode:
214            result.append("\n")
215        return "".join(result)

ODF List "text:list".

List( list_content: str | Element | collections.abc.Iterable[str | Element] | None = None, style: str | None = None, **kwargs: Any)
 74    def __init__(
 75        self,
 76        list_content: str | Element | Iterable[str | Element] | None = None,
 77        style: str | None = None,
 78        **kwargs: Any,
 79    ) -> None:
 80        """Create a list element, optionaly loading the list by a list of
 81        item (str or elements).
 82
 83        The list_content argument is just a shortcut for the most common case.
 84        To create more complex lists, first create an empty list, and fill it
 85        afterwards.
 86
 87        Arguments:
 88
 89            list_content -- str or Element, or a list of str or Element
 90
 91            style -- str
 92        """
 93        super().__init__(**kwargs)
 94        if self._do_init:
 95            if list_content:
 96                if isinstance(list_content, (Element, str)):
 97                    self.append(ListItem(list_content))
 98                elif hasattr(list_content, "__iter__"):
 99                    for item in list_content:
100                        self.append(ListItem(item))
101            if style is not None:
102                self.style = style

Create a list element, optionaly loading the list by a list of item (str or elements).

The list_content argument is just a shortcut for the most common case. To create more complex lists, first create an empty list, and fill it afterwards.

Arguments:

list_content -- str or Element, or a list of str or Element

style -- str
def get_items(self, content: str | None = None) -> list[Element]:
104    def get_items(self, content: str | None = None) -> list[Element]:
105        """Return all the list items that match the criteria.
106
107        Arguments:
108
109            style -- str
110
111            content -- str regex
112
113        Return: list of Element
114        """
115        return self._filtered_elements("text:list-item", content=content)

Return all the list items that match the criteria.

Arguments:

style -- str

content -- str regex

Return: list of Element

def get_item( self, position: int = 0, content: str | None = None) -> Element | None:
117    def get_item(
118        self,
119        position: int = 0,
120        content: str | None = None,
121    ) -> Element | None:
122        """Return the list item that matches the criteria. In nested lists,
123        return the list item that really contains that content.
124
125        Arguments:
126
127            position -- int
128
129            content -- str regex
130
131        Return: Element or None if not found
132        """
133        # Custom implementation because of nested lists
134        if content:
135            # Don't search recursively but on the very own paragraph(s) of
136            # each list item
137            for paragraph in self.get_elements("descendant::text:p"):
138                if paragraph.match(content):
139                    return paragraph.get_element("parent::text:list-item")
140            return None
141        return self._filtered_element("text:list-item", position)

Return the list item that matches the criteria. In nested lists, return the list item that really contains that content.

Arguments:

position -- int

content -- str regex

Return: Element or None if not found

def set_list_header( self, text_or_element: str | Element | collections.abc.Iterable[str | Element]) -> None:
143    def set_list_header(
144        self,
145        text_or_element: str | Element | Iterable[str | Element],
146    ) -> None:
147        if isinstance(text_or_element, (str, Element)):
148            actual_list: list[str | Element] | tuple = [text_or_element]
149        elif isinstance(text_or_element, (list, tuple)):
150            actual_list = text_or_element
151        else:
152            raise TypeError
153        # Remove existing header
154        for element in self.get_elements("text:p"):
155            self.delete(element)
156        for paragraph in reversed(actual_list):
157            if isinstance(paragraph, str):
158                paragraph = Paragraph(paragraph)
159            self.insert(paragraph, FIRST_CHILD)
def insert_item( self, item: ListItem | str | Element | None, position: int | None = None, before: Element | None = None, after: Element | None = None) -> None:
161    def insert_item(
162        self,
163        item: ListItem | str | Element | None,
164        position: int | None = None,
165        before: Element | None = None,
166        after: Element | None = None,
167    ) -> None:
168        if not isinstance(item, ListItem):
169            item = ListItem(item)
170        if before is not None:
171            before.insert(item, xmlposition=PREV_SIBLING)
172        elif after is not None:
173            after.insert(item, xmlposition=NEXT_SIBLING)
174        elif position is not None:
175            self.insert(item, position=position)
176        else:
177            raise ValueError("Position must be defined")
def append_item( self, item: ListItem | str | Element | None) -> None:
179    def append_item(
180        self,
181        item: ListItem | str | Element | None,
182    ) -> None:
183        if not isinstance(item, ListItem):
184            item = ListItem(item)
185        self.append(item)
def get_formatted_text(self, context: dict | None = None) -> str:
187    def get_formatted_text(self, context: dict | None = None) -> str:
188        if context is None:
189            context = {}
190        rst_mode = context["rst_mode"]
191        result = []
192        if rst_mode:
193            result.append("\n")
194        for list_item in self.get_elements("text:list-item"):
195            textbuf = []
196            for child in list_item.children:
197                text = child.get_formatted_text(context)
198                tag = child.tag
199                if tag == "text:h":
200                    # A title in a list is a bug
201                    return text
202                if tag == "text:list" and not text.lstrip().startswith("-"):
203                    # If the list didn't indent, don't either
204                    # (inner title)
205                    return text
206                textbuf.append(text)
207            text_sum = "".join(textbuf)
208            text_sum = text_sum.strip("\n")
209            # Indent the text
210            text_sum = text_sum.replace("\n", "\n  ")
211            text_sum = f"- {text_sum}\n"
212            result.append(text_sum)
213        if rst_mode:
214            result.append("\n")
215        return "".join(result)

This function should return a beautiful version of the text.

style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ListItem(odfdo.Element):
41class ListItem(Element):
42    """ODF element "text:list-item", item of a List."""
43
44    _tag = "text:list-item"
45
46    def __init__(
47        self,
48        text_or_element: str | Element | None = None,
49        **kwargs: Any,
50    ) -> None:
51        """Create a list item element, optionaly passing at creation time a
52        string or Element as content.
53
54        Arguments:
55
56            text_or_element -- str or ODF Element
57        """
58        super().__init__(**kwargs)
59        if self._do_init:
60            if isinstance(text_or_element, str):
61                self.text_content = text_or_element
62            elif isinstance(text_or_element, Element):
63                self.append(text_or_element)
64            elif text_or_element is not None:
65                raise TypeError("Expected str or Element")

ODF element "text:list-item", item of a List.

ListItem( text_or_element: str | Element | None = None, **kwargs: Any)
46    def __init__(
47        self,
48        text_or_element: str | Element | None = None,
49        **kwargs: Any,
50    ) -> None:
51        """Create a list item element, optionaly passing at creation time a
52        string or Element as content.
53
54        Arguments:
55
56            text_or_element -- str or ODF Element
57        """
58        super().__init__(**kwargs)
59        if self._do_init:
60            if isinstance(text_or_element, str):
61                self.text_content = text_or_element
62            elif isinstance(text_or_element, Element):
63                self.append(text_or_element)
64            elif text_or_element is not None:
65                raise TypeError("Expected str or Element")

Create a list item element, optionaly passing at creation time a string or Element as content.

Arguments:

text_or_element -- str or ODF Element
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Manifest(odfdo.XmlPart):
 31class Manifest(XmlPart):
 32    def get_paths(self) -> list[Element | Text]:
 33        """Return the list of full paths in the manifest.
 34
 35        Return: list of str
 36        """
 37        xpath_query = "//manifest:file-entry/attribute::manifest:full-path"
 38        return self.xpath(xpath_query)
 39
 40    def _file_entry(self, full_path: str) -> Element:
 41        xpath_query = (
 42            f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]'
 43        )
 44        result = self.xpath(xpath_query)
 45        if not result:
 46            raise KeyError(f"Path not found: '{full_path}'")
 47        return result[0]  # type: ignore
 48
 49    def get_path_medias(self) -> list[tuple]:
 50        """Return the list of (full_path, media_type) pairs in the manifest.
 51
 52        Return: list of str tuples
 53        """
 54        xpath_query = "//manifest:file-entry"
 55        result = []
 56        for file_entry in self.xpath(xpath_query):
 57            if not isinstance(file_entry, Element):
 58                continue
 59            result.append(
 60                (
 61                    file_entry.get_attribute_string("manifest:full-path"),
 62                    file_entry.get_attribute_string("manifest:media-type"),
 63                )
 64            )
 65        return result
 66
 67    def get_media_type(self, full_path: str) -> str | None:
 68        """Get the media type of an existing path.
 69
 70        Return: str
 71        """
 72        xpath_query = (
 73            f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]'
 74            "/attribute::manifest:media-type"
 75        )
 76        result = self.xpath(xpath_query)
 77        if not result:
 78            return None
 79        return str(result[0])
 80
 81    def set_media_type(self, full_path: str, media_type: str) -> None:
 82        """Set the media type of an existing path.
 83
 84        Arguments:
 85
 86            full_path -- str
 87
 88            media_type -- str
 89        """
 90        file_entry = self._file_entry(full_path)
 91        file_entry.set_attribute("manifest:media-type", media_type)
 92
 93    @staticmethod
 94    def make_file_entry(full_path: str, media_type: str) -> Element:
 95        tag = (
 96            f"<manifest:file-entry "
 97            f'manifest:media-type="{media_type}" '
 98            f'manifest:full-path="{full_path}"/>'
 99        )
100        return Element.from_tag(tag)
101
102    def add_full_path(self, full_path: str, media_type: str = "") -> None:
103        # Existing?
104        existing = self.get_media_type(full_path)
105        if existing is not None:
106            self.set_media_type(full_path, media_type)
107        root = self.root
108        root.append(self.make_file_entry(full_path, media_type))
109
110    def del_full_path(self, full_path: str) -> None:
111        file_entry = self._file_entry(full_path)
112        self.root.delete(file_entry)

Representation of an XML part.

Abstraction of the XML library behind.

def get_paths(self) -> list[Element | Text]:
32    def get_paths(self) -> list[Element | Text]:
33        """Return the list of full paths in the manifest.
34
35        Return: list of str
36        """
37        xpath_query = "//manifest:file-entry/attribute::manifest:full-path"
38        return self.xpath(xpath_query)

Return the list of full paths in the manifest.

Return: list of str

def get_path_medias(self) -> list[tuple]:
49    def get_path_medias(self) -> list[tuple]:
50        """Return the list of (full_path, media_type) pairs in the manifest.
51
52        Return: list of str tuples
53        """
54        xpath_query = "//manifest:file-entry"
55        result = []
56        for file_entry in self.xpath(xpath_query):
57            if not isinstance(file_entry, Element):
58                continue
59            result.append(
60                (
61                    file_entry.get_attribute_string("manifest:full-path"),
62                    file_entry.get_attribute_string("manifest:media-type"),
63                )
64            )
65        return result

Return the list of (full_path, media_type) pairs in the manifest.

Return: list of str tuples

def get_media_type(self, full_path: str) -> str | None:
67    def get_media_type(self, full_path: str) -> str | None:
68        """Get the media type of an existing path.
69
70        Return: str
71        """
72        xpath_query = (
73            f'//manifest:file-entry[attribute::manifest:full-path="{full_path}"]'
74            "/attribute::manifest:media-type"
75        )
76        result = self.xpath(xpath_query)
77        if not result:
78            return None
79        return str(result[0])

Get the media type of an existing path.

Return: str

def set_media_type(self, full_path: str, media_type: str) -> None:
81    def set_media_type(self, full_path: str, media_type: str) -> None:
82        """Set the media type of an existing path.
83
84        Arguments:
85
86            full_path -- str
87
88            media_type -- str
89        """
90        file_entry = self._file_entry(full_path)
91        file_entry.set_attribute("manifest:media-type", media_type)

Set the media type of an existing path.

Arguments:

full_path -- str

media_type -- str
@staticmethod
def make_file_entry(full_path: str, media_type: str) -> Element:
 93    @staticmethod
 94    def make_file_entry(full_path: str, media_type: str) -> Element:
 95        tag = (
 96            f"<manifest:file-entry "
 97            f'manifest:media-type="{media_type}" '
 98            f'manifest:full-path="{full_path}"/>'
 99        )
100        return Element.from_tag(tag)
def add_full_path(self, full_path: str, media_type: str = '') -> None:
102    def add_full_path(self, full_path: str, media_type: str = "") -> None:
103        # Existing?
104        existing = self.get_media_type(full_path)
105        if existing is not None:
106            self.set_media_type(full_path, media_type)
107        root = self.root
108        root.append(self.make_file_entry(full_path, media_type))
def del_full_path(self, full_path: str) -> None:
110    def del_full_path(self, full_path: str) -> None:
111        file_entry = self._file_entry(full_path)
112        self.root.delete(file_entry)
class Meta(odfdo.XmlPart):
 43class Meta(XmlPart):
 44    def __init__(self, *args: Any, **kwargs: Any) -> None:
 45        super().__init__(*args, **kwargs)
 46        self._generator_modified: bool = False
 47
 48    def get_meta_body(self) -> Element:
 49        return self.get_element("//office:meta")
 50
 51    def get_title(self) -> str | None:
 52        """Get the title of the document.
 53
 54        This is not the first heading but the title metadata.
 55
 56        Return: str (or None if inexistant)
 57        """
 58        element = self.get_element("//dc:title")
 59        if element is None:
 60            return None
 61        return element.text
 62
 63    def set_title(self, title: str) -> None:
 64        """Set the title of the document.
 65
 66        This is not the first heading but the title metadata.
 67
 68        Arguments:
 69
 70            title -- str
 71        """
 72        element = self.get_element("//dc:title")
 73        if element is None:
 74            element = Element.from_tag("dc:title")
 75            self.get_meta_body().append(element)
 76        element.text = title
 77
 78    def get_description(self) -> str | None:
 79        """Get the description of the document. Also known as comments.
 80
 81        Return: str (or None if inexistant)
 82        """
 83        element = self.get_element("//dc:description")
 84        if element is None:
 85            return None
 86        return element.text
 87
 88    # As named in OOo
 89    get_comments = get_description
 90
 91    def set_description(self, description: str) -> None:
 92        """Set the description of the document. Also known as comments.
 93
 94        Arguments:
 95
 96            description -- str
 97        """
 98        element = self.get_element("//dc:description")
 99        if element is None:
100            element = Element.from_tag("dc:description")
101            self.get_meta_body().append(element)
102        element.text = description
103
104    set_comments = set_description
105
106    def get_subject(self) -> str | None:
107        """Get the subject of the document.
108
109        Return: str (or None if inexistant)
110        """
111        element = self.get_element("//dc:subject")
112        if element is None:
113            return None
114        return element.text
115
116    def set_subject(self, subject: str) -> None:
117        """Set the subject of the document.
118
119        Arguments:
120
121            subject -- str
122        """
123        element = self.get_element("//dc:subject")
124        if element is None:
125            element = Element.from_tag("dc:subject")
126            self.get_meta_body().append(element)
127        element.text = subject
128
129    def get_language(self) -> str | None:
130        """Get the language code of the document.
131
132        Return: str (or None if inexistant)
133
134        Example::
135
136            >>> document.meta.get_language()
137            fr-FR
138        """
139        element = self.get_element("//dc:language")
140        if element is None:
141            return None
142        return element.text
143
144    def set_language(self, language: str) -> None:
145        """Set the language code of the document.
146
147        Arguments:
148
149            language -- str
150
151        Example::
152
153            >>> document.meta.set_language('fr-FR')
154        """
155        language = str(language)
156        if not self._is_RFC3066(language):
157            raise TypeError(
158                'Language must be "xx" lang or "xx-YY" lang-COUNTRY code (RFC3066)'
159            )
160        element = self.get_element("//dc:language")
161        if element is None:
162            element = Element.from_tag("dc:language")
163            self.get_meta_body().append(element)
164        element.text = language
165
166    @staticmethod
167    def _is_RFC3066(lang: str) -> bool:
168        def test_part1(part1: str) -> bool:
169            if not 2 <= len(part1) <= 3:
170                return False
171            return all(x in ascii_letters for x in part1)
172
173        def test_part2(part2: str) -> bool:
174            return all(x in ascii_letters or x in digits for x in part2)
175
176        if not lang or not isinstance(lang, str):
177            return False
178        if "-" not in lang:
179            return test_part1(lang)
180        parts = lang.split("-")
181        if len(parts) > 3:
182            return False
183        if not test_part1(parts[0]):
184            return False
185        return all(test_part2(p) for p in parts[1:])
186
187    def get_modification_date(self) -> datetime | None:
188        """Get the last modified date of the document.
189
190        Return: datetime (or None if inexistant)
191        """
192        element = self.get_element("//dc:date")
193        if element is None:
194            return None
195        modification_date = element.text
196        return DateTime.decode(modification_date)
197
198    def set_modification_date(self, date: datetime) -> None:
199        """Set the last modified date of the document.
200
201        Arguments:
202
203            date -- datetime
204        """
205        element = self.get_element("//dc:date")
206        if element is None:
207            element = Element.from_tag("dc:date")
208            self.get_meta_body().append(element)
209        element.text = DateTime.encode(date)
210
211    def get_creation_date(self) -> datetime | None:
212        """Get the creation date of the document.
213
214        Return: datetime (or None if inexistant)
215        """
216        element = self.get_element("//meta:creation-date")
217        if element is None:
218            return None
219        creation_date = element.text
220        return DateTime.decode(creation_date)
221
222    def set_creation_date(self, date: datetime) -> None:
223        """Set the creation date of the document.
224
225        Arguments:
226
227            date -- datetime
228        """
229        element = self.get_element("//meta:creation-date")
230        if element is None:
231            element = Element.from_tag("meta:creation-date")
232            self.get_meta_body().append(element)
233        element.text = DateTime.encode(date)
234
235    def get_initial_creator(self) -> str | None:
236        """Get the first creator of the document.
237
238        Return: str (or None if inexistant)
239
240        Example::
241
242            >>> document.meta.get_initial_creator()
243            Unknown
244        """
245        element = self.get_element("//meta:initial-creator")
246        if element is None:
247            return None
248        return element.text
249
250    def set_initial_creator(self, creator: str) -> None:
251        """Set the first creator of the document.
252
253        Arguments:
254
255            creator -- str
256
257        Example::
258
259            >>> document.meta.set_initial_creator("Plato")
260        """
261        element = self.get_element("//meta:initial-creator")
262        if element is None:
263            element = Element.from_tag("meta:initial-creator")
264            self.get_meta_body().append(element)
265        element.text = creator
266
267    def get_creator(self) -> str | None:
268        """Get the creator of the document.
269
270        Return: str (or None if inexistant)
271
272        Example::
273
274            >>> document.meta.get_creator()
275            Unknown
276        """
277        element = self.get_element("//dc:creator")
278        if element is None:
279            return None
280        return element.text
281
282    def set_creator(self, creator: str) -> None:
283        """Set the creator of the document.
284
285        Arguments:
286
287            creator -- str
288
289        Example::
290
291            >>> document.meta.set_creator("Plato")
292        """
293        element = self.get_element("//dc:creator")
294        if element is None:
295            element = Element.from_tag("dc:creator")
296            self.get_meta_body().append(element)
297        element.text = creator
298
299    def get_keywords(self) -> str | None:
300        """Get the keywords of the document. Return the field as-is, without
301        any assumption on the keyword separator.
302
303        Return: str (or None if inexistant)
304        """
305        element = self.get_element("//meta:keyword")
306        if element is None:
307            return None
308        return element.text
309
310    def set_keywords(self, keywords: str) -> None:
311        """Set the keywords of the document. Although the name is plural, a
312        str string is required, so join your list first.
313
314        Arguments:
315
316            keywords -- str
317        """
318        element = self.get_element("//meta:keyword")
319        if element is None:
320            element = Element.from_tag("meta:keyword")
321            self.get_meta_body().append(element)
322        element.text = keywords
323
324    def get_editing_duration(self) -> timedelta | None:
325        """Get the time the document was edited, as reported by the
326        generator.
327
328        Return: timedelta (or None if inexistant)
329        """
330        element = self.get_element("//meta:editing-duration")
331        if element is None:
332            return None
333        duration = element.text
334        return Duration.decode(duration)
335
336    def set_editing_duration(self, duration: timedelta) -> None:
337        """Set the time the document was edited.
338
339        Arguments:
340
341            duration -- timedelta
342        """
343        if not isinstance(duration, timedelta):
344            raise TypeError("duration must be a timedelta")
345        element = self.get_element("//meta:editing-duration")
346        if element is None:
347            element = Element.from_tag("meta:editing-duration")
348            self.get_meta_body().append(element)
349        element.text = Duration.encode(duration)
350
351    def get_editing_cycles(self) -> int | None:
352        """Get the number of times the document was edited, as reported by
353        the generator.
354
355        Return: int (or None if inexistant)
356        """
357        element = self.get_element("//meta:editing-cycles")
358        if element is None:
359            return None
360        cycles = element.text
361        return int(cycles)
362
363    def set_editing_cycles(self, cycles: int) -> None:
364        """Set the number of times the document was edited.
365
366        Arguments:
367
368            cycles -- int
369        """
370        if not isinstance(cycles, int):
371            raise TypeError("cycles must be an int")
372        if cycles < 1:
373            raise ValueError("cycles must be a positive int")
374        element = self.get_element("//meta:editing-cycles")
375        if element is None:
376            element = Element.from_tag("meta:editing-cycles")
377            self.get_meta_body().append(element)
378        element.text = str(cycles)
379
380    def get_generator(self) -> str | None:
381        """Get the signature of the software that generated this document.
382
383        Return: str (or None if inexistant)
384
385        Example::
386
387            >>> document.meta.get_generator()
388            KOffice/2.0.0
389        """
390        element = self.get_element("//meta:generator")
391        if element is None:
392            return None
393        return element.text
394
395    def set_generator(self, generator: str) -> None:
396        """Set the signature of the software that generated this document.
397
398        Arguments:
399
400            generator -- str
401
402        Example::
403
404            >>> document.meta.set_generator("Odfdo experiment")
405        """
406        element = self.get_element("//meta:generator")
407        if element is None:
408            element = Element.from_tag("meta:generator")
409            self.get_meta_body().append(element)
410        element.text = generator
411        self._generator_modified = True
412
413    def set_generator_default(self) -> None:
414        """Set the signature of the software that generated this document
415        to ourself.
416
417        Example::
418
419            >>> document.meta.set_generator_default()
420        """
421        if not self._generator_modified:
422            self.set_generator(GENERATOR)
423
424    def get_statistic(self) -> dict[str, int] | None:
425        """Get the statistic from the software that generated this document.
426
427        Return: dict (or None if inexistant)
428
429        Example::
430
431            >>> document.get_statistic():
432            {'meta:table-count': 1,
433             'meta:image-count': 2,
434             'meta:object-count': 3,
435             'meta:page-count': 4,
436             'meta:paragraph-count': 5,
437             'meta:word-count': 6,
438             'meta:character-count': 7}
439        """
440        element = self.get_element("//meta:document-statistic")
441        if element is None:
442            return None
443        statistic = {}
444        for key, value in element.attributes.items():
445            statistic[to_str(key)] = int(value)
446        return statistic
447
448    def set_statistic(self, statistic: dict[str, int]) -> None:
449        """Set the statistic for the documents: number of words, paragraphs,
450        etc.
451
452        Arguments:
453
454            statistic -- dict
455
456        Example::
457
458            >>> statistic = {'meta:table-count': 1,
459                             'meta:image-count': 2,
460                             'meta:object-count': 3,
461                             'meta:page-count': 4,
462                             'meta:paragraph-count': 5,
463                             'meta:word-count': 6,
464                             'meta:character-count': 7}
465            >>> document.meta.set_statistic(statistic)
466        """
467        if not isinstance(statistic, dict):
468            raise TypeError("Statistic must be a dict")
469        element = self.get_element("//meta:document-statistic")
470        for key, value in statistic.items():
471            try:
472                ivalue = int(value)
473            except ValueError as e:
474                raise TypeError("Statistic value must be a int") from e
475            element.set_attribute(to_str(key), str(ivalue))
476
477    def get_user_defined_metadata(self) -> dict[str, Any]:
478        """Return a dict of str/value mapping.
479
480        Value types can be: Decimal, date, time, boolean or str.
481        """
482        result: dict[str, Any] = {}
483        for item in self.get_elements("//meta:user-defined"):
484            if not isinstance(item, Element):
485                continue
486            # Read the values
487            name = item.get_attribute_string("meta:name")
488            if name is None:
489                continue
490            value = self._get_meta_value(item)
491            result[name] = value
492        return result
493
494    def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, Any] | None:
495        """Return the content of the user defined metadata of that name.
496        Return None if no name matchs or a dic of fields.
497
498        Arguments:
499
500            name -- string, name (meta:name content)
501        """
502        result = {}
503        found = False
504        for item in self.get_elements("//meta:user-defined"):
505            if not isinstance(item, Element):
506                continue
507            # Read the values
508            name = item.get_attribute("meta:name")
509            if name == keyname:
510                found = True
511                break
512        if not found:
513            return None
514        result["name"] = name
515        value, value_type, text = self._get_meta_value(item, full=True)  # type: ignore
516        result["value"] = value
517        result["value_type"] = value_type
518        result["text"] = text
519        return result
520
521    def set_user_defined_metadata(self, name: str, value: Any) -> None:
522        if isinstance(value, bool):
523            value_type = "boolean"
524            value = "true" if value else "false"
525        elif isinstance(value, (int, float, Decimal)):
526            value_type = "float"
527            value = str(value)
528        elif isinstance(value, dtdate):
529            value_type = "date"
530            value = str(Date.encode(value))
531        elif isinstance(value, datetime):
532            value_type = "date"
533            value = str(DateTime.encode(value))
534        elif isinstance(value, str):
535            value_type = "string"
536        elif isinstance(value, timedelta):
537            value_type = "time"
538            value = str(Duration.encode(value))
539        else:
540            raise TypeError('unexpected type "%s" for value' % type(value))
541        # Already the same element ?
542        for metadata in self.get_elements("//meta:user-defined"):
543            if not isinstance(metadata, Element):
544                continue
545            if metadata.get_attribute("meta:name") == name:
546                break
547        else:
548            metadata = Element.from_tag("meta:user-defined")
549            metadata.set_attribute("meta:name", name)
550            self.get_meta_body().append(metadata)
551        metadata.set_attribute("meta:value-type", value_type)
552        metadata.text = value
553
554    def _get_meta_value(
555        self, element: Element, full: bool = False
556    ) -> Any | tuple[Any, str, str]:
557        """get_value() deicated to the meta data part, for one meta element."""
558        if full:
559            return self._get_meta_value_full(element)
560        else:
561            return self._get_meta_value_full(element)[0]
562
563    @staticmethod
564    def _get_meta_value_full(element: Element) -> tuple[Any, str, str]:
565        """get_value deicated to the meta data part, for one meta element."""
566        # name = element.get_attribute('meta:name')
567        value_type = element.get_attribute_string("meta:value-type")
568        if value_type is None:
569            value_type = "string"
570        text = element.text
571        # Interpretation
572        if value_type == "boolean":
573            return (Boolean.decode(text), value_type, text)
574        if value_type in ("float", "percentage", "currency"):
575            return (Decimal(text), value_type, text)
576        if value_type == "date":
577            if "T" in text:
578                return (DateTime.decode(text), value_type, text)
579            else:
580                return (Date.decode(text), value_type, text)
581        if value_type == "string":
582            return (text, value_type, text)
583        if value_type == "time":
584            return (Duration.decode(text), value_type, text)
585        raise TypeError(f"Unknown value type: '{value_type!r}'")

Representation of an XML part.

Abstraction of the XML library behind.

Meta(*args: Any, **kwargs: Any)
44    def __init__(self, *args: Any, **kwargs: Any) -> None:
45        super().__init__(*args, **kwargs)
46        self._generator_modified: bool = False
def get_meta_body(self) -> Element:
48    def get_meta_body(self) -> Element:
49        return self.get_element("//office:meta")
def get_title(self) -> str | None:
51    def get_title(self) -> str | None:
52        """Get the title of the document.
53
54        This is not the first heading but the title metadata.
55
56        Return: str (or None if inexistant)
57        """
58        element = self.get_element("//dc:title")
59        if element is None:
60            return None
61        return element.text

Get the title of the document.

This is not the first heading but the title metadata.

Return: str (or None if inexistant)

def set_title(self, title: str) -> None:
63    def set_title(self, title: str) -> None:
64        """Set the title of the document.
65
66        This is not the first heading but the title metadata.
67
68        Arguments:
69
70            title -- str
71        """
72        element = self.get_element("//dc:title")
73        if element is None:
74            element = Element.from_tag("dc:title")
75            self.get_meta_body().append(element)
76        element.text = title

Set the title of the document.

This is not the first heading but the title metadata.

Arguments:

title -- str
def get_description(self) -> str | None:
78    def get_description(self) -> str | None:
79        """Get the description of the document. Also known as comments.
80
81        Return: str (or None if inexistant)
82        """
83        element = self.get_element("//dc:description")
84        if element is None:
85            return None
86        return element.text

Get the description of the document. Also known as comments.

Return: str (or None if inexistant)

def get_comments(self) -> str | None:
78    def get_description(self) -> str | None:
79        """Get the description of the document. Also known as comments.
80
81        Return: str (or None if inexistant)
82        """
83        element = self.get_element("//dc:description")
84        if element is None:
85            return None
86        return element.text

Get the description of the document. Also known as comments.

Return: str (or None if inexistant)

def set_description(self, description: str) -> None:
 91    def set_description(self, description: str) -> None:
 92        """Set the description of the document. Also known as comments.
 93
 94        Arguments:
 95
 96            description -- str
 97        """
 98        element = self.get_element("//dc:description")
 99        if element is None:
100            element = Element.from_tag("dc:description")
101            self.get_meta_body().append(element)
102        element.text = description

Set the description of the document. Also known as comments.

Arguments:

description -- str
def set_comments(self, description: str) -> None:
 91    def set_description(self, description: str) -> None:
 92        """Set the description of the document. Also known as comments.
 93
 94        Arguments:
 95
 96            description -- str
 97        """
 98        element = self.get_element("//dc:description")
 99        if element is None:
100            element = Element.from_tag("dc:description")
101            self.get_meta_body().append(element)
102        element.text = description

Set the description of the document. Also known as comments.

Arguments:

description -- str
def get_subject(self) -> str | None:
106    def get_subject(self) -> str | None:
107        """Get the subject of the document.
108
109        Return: str (or None if inexistant)
110        """
111        element = self.get_element("//dc:subject")
112        if element is None:
113            return None
114        return element.text

Get the subject of the document.

Return: str (or None if inexistant)

def set_subject(self, subject: str) -> None:
116    def set_subject(self, subject: str) -> None:
117        """Set the subject of the document.
118
119        Arguments:
120
121            subject -- str
122        """
123        element = self.get_element("//dc:subject")
124        if element is None:
125            element = Element.from_tag("dc:subject")
126            self.get_meta_body().append(element)
127        element.text = subject

Set the subject of the document.

Arguments:

subject -- str
def get_language(self) -> str | None:
129    def get_language(self) -> str | None:
130        """Get the language code of the document.
131
132        Return: str (or None if inexistant)
133
134        Example::
135
136            >>> document.meta.get_language()
137            fr-FR
138        """
139        element = self.get_element("//dc:language")
140        if element is None:
141            return None
142        return element.text

Get the language code of the document.

Return: str (or None if inexistant)

Example::

>>> document.meta.get_language()
fr-FR
def set_language(self, language: str) -> None:
144    def set_language(self, language: str) -> None:
145        """Set the language code of the document.
146
147        Arguments:
148
149            language -- str
150
151        Example::
152
153            >>> document.meta.set_language('fr-FR')
154        """
155        language = str(language)
156        if not self._is_RFC3066(language):
157            raise TypeError(
158                'Language must be "xx" lang or "xx-YY" lang-COUNTRY code (RFC3066)'
159            )
160        element = self.get_element("//dc:language")
161        if element is None:
162            element = Element.from_tag("dc:language")
163            self.get_meta_body().append(element)
164        element.text = language

Set the language code of the document.

Arguments:

language -- str

Example::

>>> document.meta.set_language('fr-FR')
def get_modification_date(self) -> datetime.datetime | None:
187    def get_modification_date(self) -> datetime | None:
188        """Get the last modified date of the document.
189
190        Return: datetime (or None if inexistant)
191        """
192        element = self.get_element("//dc:date")
193        if element is None:
194            return None
195        modification_date = element.text
196        return DateTime.decode(modification_date)

Get the last modified date of the document.

Return: datetime (or None if inexistant)

def set_modification_date(self, date: datetime.datetime) -> None:
198    def set_modification_date(self, date: datetime) -> None:
199        """Set the last modified date of the document.
200
201        Arguments:
202
203            date -- datetime
204        """
205        element = self.get_element("//dc:date")
206        if element is None:
207            element = Element.from_tag("dc:date")
208            self.get_meta_body().append(element)
209        element.text = DateTime.encode(date)

Set the last modified date of the document.

Arguments:

date -- datetime
def get_creation_date(self) -> datetime.datetime | None:
211    def get_creation_date(self) -> datetime | None:
212        """Get the creation date of the document.
213
214        Return: datetime (or None if inexistant)
215        """
216        element = self.get_element("//meta:creation-date")
217        if element is None:
218            return None
219        creation_date = element.text
220        return DateTime.decode(creation_date)

Get the creation date of the document.

Return: datetime (or None if inexistant)

def set_creation_date(self, date: datetime.datetime) -> None:
222    def set_creation_date(self, date: datetime) -> None:
223        """Set the creation date of the document.
224
225        Arguments:
226
227            date -- datetime
228        """
229        element = self.get_element("//meta:creation-date")
230        if element is None:
231            element = Element.from_tag("meta:creation-date")
232            self.get_meta_body().append(element)
233        element.text = DateTime.encode(date)

Set the creation date of the document.

Arguments:

date -- datetime
def get_initial_creator(self) -> str | None:
235    def get_initial_creator(self) -> str | None:
236        """Get the first creator of the document.
237
238        Return: str (or None if inexistant)
239
240        Example::
241
242            >>> document.meta.get_initial_creator()
243            Unknown
244        """
245        element = self.get_element("//meta:initial-creator")
246        if element is None:
247            return None
248        return element.text

Get the first creator of the document.

Return: str (or None if inexistant)

Example::

>>> document.meta.get_initial_creator()
Unknown
def set_initial_creator(self, creator: str) -> None:
250    def set_initial_creator(self, creator: str) -> None:
251        """Set the first creator of the document.
252
253        Arguments:
254
255            creator -- str
256
257        Example::
258
259            >>> document.meta.set_initial_creator("Plato")
260        """
261        element = self.get_element("//meta:initial-creator")
262        if element is None:
263            element = Element.from_tag("meta:initial-creator")
264            self.get_meta_body().append(element)
265        element.text = creator

Set the first creator of the document.

Arguments:

creator -- str

Example::

>>> document.meta.set_initial_creator("Plato")
def get_creator(self) -> str | None:
267    def get_creator(self) -> str | None:
268        """Get the creator of the document.
269
270        Return: str (or None if inexistant)
271
272        Example::
273
274            >>> document.meta.get_creator()
275            Unknown
276        """
277        element = self.get_element("//dc:creator")
278        if element is None:
279            return None
280        return element.text

Get the creator of the document.

Return: str (or None if inexistant)

Example::

>>> document.meta.get_creator()
Unknown
def set_creator(self, creator: str) -> None:
282    def set_creator(self, creator: str) -> None:
283        """Set the creator of the document.
284
285        Arguments:
286
287            creator -- str
288
289        Example::
290
291            >>> document.meta.set_creator("Plato")
292        """
293        element = self.get_element("//dc:creator")
294        if element is None:
295            element = Element.from_tag("dc:creator")
296            self.get_meta_body().append(element)
297        element.text = creator

Set the creator of the document.

Arguments:

creator -- str

Example::

>>> document.meta.set_creator("Plato")
def get_keywords(self) -> str | None:
299    def get_keywords(self) -> str | None:
300        """Get the keywords of the document. Return the field as-is, without
301        any assumption on the keyword separator.
302
303        Return: str (or None if inexistant)
304        """
305        element = self.get_element("//meta:keyword")
306        if element is None:
307            return None
308        return element.text

Get the keywords of the document. Return the field as-is, without any assumption on the keyword separator.

Return: str (or None if inexistant)

def set_keywords(self, keywords: str) -> None:
310    def set_keywords(self, keywords: str) -> None:
311        """Set the keywords of the document. Although the name is plural, a
312        str string is required, so join your list first.
313
314        Arguments:
315
316            keywords -- str
317        """
318        element = self.get_element("//meta:keyword")
319        if element is None:
320            element = Element.from_tag("meta:keyword")
321            self.get_meta_body().append(element)
322        element.text = keywords

Set the keywords of the document. Although the name is plural, a str string is required, so join your list first.

Arguments:

keywords -- str
def get_editing_duration(self) -> datetime.timedelta | None:
324    def get_editing_duration(self) -> timedelta | None:
325        """Get the time the document was edited, as reported by the
326        generator.
327
328        Return: timedelta (or None if inexistant)
329        """
330        element = self.get_element("//meta:editing-duration")
331        if element is None:
332            return None
333        duration = element.text
334        return Duration.decode(duration)

Get the time the document was edited, as reported by the generator.

Return: timedelta (or None if inexistant)

def set_editing_duration(self, duration: datetime.timedelta) -> None:
336    def set_editing_duration(self, duration: timedelta) -> None:
337        """Set the time the document was edited.
338
339        Arguments:
340
341            duration -- timedelta
342        """
343        if not isinstance(duration, timedelta):
344            raise TypeError("duration must be a timedelta")
345        element = self.get_element("//meta:editing-duration")
346        if element is None:
347            element = Element.from_tag("meta:editing-duration")
348            self.get_meta_body().append(element)
349        element.text = Duration.encode(duration)

Set the time the document was edited.

Arguments:

duration -- timedelta
def get_editing_cycles(self) -> int | None:
351    def get_editing_cycles(self) -> int | None:
352        """Get the number of times the document was edited, as reported by
353        the generator.
354
355        Return: int (or None if inexistant)
356        """
357        element = self.get_element("//meta:editing-cycles")
358        if element is None:
359            return None
360        cycles = element.text
361        return int(cycles)

Get the number of times the document was edited, as reported by the generator.

Return: int (or None if inexistant)

def set_editing_cycles(self, cycles: int) -> None:
363    def set_editing_cycles(self, cycles: int) -> None:
364        """Set the number of times the document was edited.
365
366        Arguments:
367
368            cycles -- int
369        """
370        if not isinstance(cycles, int):
371            raise TypeError("cycles must be an int")
372        if cycles < 1:
373            raise ValueError("cycles must be a positive int")
374        element = self.get_element("//meta:editing-cycles")
375        if element is None:
376            element = Element.from_tag("meta:editing-cycles")
377            self.get_meta_body().append(element)
378        element.text = str(cycles)

Set the number of times the document was edited.

Arguments:

cycles -- int
def get_generator(self) -> str | None:
380    def get_generator(self) -> str | None:
381        """Get the signature of the software that generated this document.
382
383        Return: str (or None if inexistant)
384
385        Example::
386
387            >>> document.meta.get_generator()
388            KOffice/2.0.0
389        """
390        element = self.get_element("//meta:generator")
391        if element is None:
392            return None
393        return element.text

Get the signature of the software that generated this document.

Return: str (or None if inexistant)

Example::

>>> document.meta.get_generator()
KOffice/2.0.0
def set_generator(self, generator: str) -> None:
395    def set_generator(self, generator: str) -> None:
396        """Set the signature of the software that generated this document.
397
398        Arguments:
399
400            generator -- str
401
402        Example::
403
404            >>> document.meta.set_generator("Odfdo experiment")
405        """
406        element = self.get_element("//meta:generator")
407        if element is None:
408            element = Element.from_tag("meta:generator")
409            self.get_meta_body().append(element)
410        element.text = generator
411        self._generator_modified = True

Set the signature of the software that generated this document.

Arguments:

generator -- str

Example::

>>> document.meta.set_generator("Odfdo experiment")
def set_generator_default(self) -> None:
413    def set_generator_default(self) -> None:
414        """Set the signature of the software that generated this document
415        to ourself.
416
417        Example::
418
419            >>> document.meta.set_generator_default()
420        """
421        if not self._generator_modified:
422            self.set_generator(GENERATOR)

Set the signature of the software that generated this document to ourself.

Example::

>>> document.meta.set_generator_default()
def get_statistic(self) -> dict[str, int] | None:
424    def get_statistic(self) -> dict[str, int] | None:
425        """Get the statistic from the software that generated this document.
426
427        Return: dict (or None if inexistant)
428
429        Example::
430
431            >>> document.get_statistic():
432            {'meta:table-count': 1,
433             'meta:image-count': 2,
434             'meta:object-count': 3,
435             'meta:page-count': 4,
436             'meta:paragraph-count': 5,
437             'meta:word-count': 6,
438             'meta:character-count': 7}
439        """
440        element = self.get_element("//meta:document-statistic")
441        if element is None:
442            return None
443        statistic = {}
444        for key, value in element.attributes.items():
445            statistic[to_str(key)] = int(value)
446        return statistic

Get the statistic from the software that generated this document.

Return: dict (or None if inexistant)

Example::

>>> document.get_statistic():
{'meta:table-count': 1,
 'meta:image-count': 2,
 'meta:object-count': 3,
 'meta:page-count': 4,
 'meta:paragraph-count': 5,
 'meta:word-count': 6,
 'meta:character-count': 7}
def set_statistic(self, statistic: dict[str, int]) -> None:
448    def set_statistic(self, statistic: dict[str, int]) -> None:
449        """Set the statistic for the documents: number of words, paragraphs,
450        etc.
451
452        Arguments:
453
454            statistic -- dict
455
456        Example::
457
458            >>> statistic = {'meta:table-count': 1,
459                             'meta:image-count': 2,
460                             'meta:object-count': 3,
461                             'meta:page-count': 4,
462                             'meta:paragraph-count': 5,
463                             'meta:word-count': 6,
464                             'meta:character-count': 7}
465            >>> document.meta.set_statistic(statistic)
466        """
467        if not isinstance(statistic, dict):
468            raise TypeError("Statistic must be a dict")
469        element = self.get_element("//meta:document-statistic")
470        for key, value in statistic.items():
471            try:
472                ivalue = int(value)
473            except ValueError as e:
474                raise TypeError("Statistic value must be a int") from e
475            element.set_attribute(to_str(key), str(ivalue))

Set the statistic for the documents: number of words, paragraphs, etc.

Arguments:

statistic -- dict

Example::

>>> statistic = {'meta:table-count': 1,
                 'meta:image-count': 2,
                 'meta:object-count': 3,
                 'meta:page-count': 4,
                 'meta:paragraph-count': 5,
                 'meta:word-count': 6,
                 'meta:character-count': 7}
>>> document.meta.set_statistic(statistic)
def get_user_defined_metadata(self) -> dict[str, typing.Any]:
477    def get_user_defined_metadata(self) -> dict[str, Any]:
478        """Return a dict of str/value mapping.
479
480        Value types can be: Decimal, date, time, boolean or str.
481        """
482        result: dict[str, Any] = {}
483        for item in self.get_elements("//meta:user-defined"):
484            if not isinstance(item, Element):
485                continue
486            # Read the values
487            name = item.get_attribute_string("meta:name")
488            if name is None:
489                continue
490            value = self._get_meta_value(item)
491            result[name] = value
492        return result

Return a dict of str/value mapping.

Value types can be: Decimal, date, time, boolean or str.

def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, typing.Any] | None:
494    def get_user_defined_metadata_of_name(self, keyname: str) -> dict[str, Any] | None:
495        """Return the content of the user defined metadata of that name.
496        Return None if no name matchs or a dic of fields.
497
498        Arguments:
499
500            name -- string, name (meta:name content)
501        """
502        result = {}
503        found = False
504        for item in self.get_elements("//meta:user-defined"):
505            if not isinstance(item, Element):
506                continue
507            # Read the values
508            name = item.get_attribute("meta:name")
509            if name == keyname:
510                found = True
511                break
512        if not found:
513            return None
514        result["name"] = name
515        value, value_type, text = self._get_meta_value(item, full=True)  # type: ignore
516        result["value"] = value
517        result["value_type"] = value_type
518        result["text"] = text
519        return result

Return the content of the user defined metadata of that name. Return None if no name matchs or a dic of fields.

Arguments:

name -- string, name (meta:name content)
def set_user_defined_metadata(self, name: str, value: Any) -> None:
521    def set_user_defined_metadata(self, name: str, value: Any) -> None:
522        if isinstance(value, bool):
523            value_type = "boolean"
524            value = "true" if value else "false"
525        elif isinstance(value, (int, float, Decimal)):
526            value_type = "float"
527            value = str(value)
528        elif isinstance(value, dtdate):
529            value_type = "date"
530            value = str(Date.encode(value))
531        elif isinstance(value, datetime):
532            value_type = "date"
533            value = str(DateTime.encode(value))
534        elif isinstance(value, str):
535            value_type = "string"
536        elif isinstance(value, timedelta):
537            value_type = "time"
538            value = str(Duration.encode(value))
539        else:
540            raise TypeError('unexpected type "%s" for value' % type(value))
541        # Already the same element ?
542        for metadata in self.get_elements("//meta:user-defined"):
543            if not isinstance(metadata, Element):
544                continue
545            if metadata.get_attribute("meta:name") == name:
546                break
547        else:
548            metadata = Element.from_tag("meta:user-defined")
549            metadata.set_attribute("meta:name", name)
550            self.get_meta_body().append(metadata)
551        metadata.set_attribute("meta:value-type", value_type)
552        metadata.text = value
NEXT_SIBLING = 2
class NamedRange(odfdo.Element):
2909class NamedRange(Element):
2910    """ODF Named Range "table:named-range". Identifies inside the spreadsheet
2911    a range of cells of a table by a name and the name of the table.
2912
2913    Name Ranges have the following attributes:
2914
2915        name -- name of the named range
2916
2917        table_name -- name of the table
2918
2919        start -- first cell of the named range, tuple (x, y)
2920
2921        end -- last cell of the named range, tuple (x, y)
2922
2923        crange -- range of the named range, tuple (x, y, z, t)
2924
2925        usage -- None or str, usage of the named range.
2926    """
2927
2928    _tag = "table:named-range"
2929
2930    def __init__(
2931        self,
2932        name: str | None = None,
2933        crange: str | tuple | list | None = None,
2934        table_name: str | None = None,
2935        usage: str | None = None,
2936        **kwargs: Any,
2937    ) -> None:
2938        """Create a Named Range element. 'name' must contains only letters, digits
2939           and '_', and must not be like a coordinate as 'A1'. 'table_name' must be
2940           a correct table name (no "'" or "/" in it).
2941
2942        Arguments:
2943
2944             name -- str, name of the named range
2945
2946             crange -- str or tuple of int, cell or area coordinate
2947
2948             table_name -- str, name of the table
2949
2950             usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2951        """
2952        super().__init__(**kwargs)
2953        self.usage = None
2954        if self._do_init:
2955            self.name = name or ""
2956            self.table_name = _table_name_check(table_name)
2957            self.set_range(crange or "")
2958            self.set_usage(usage)
2959        cell_range_address = self.get_attribute_string("table:cell-range-address") or ""
2960        if not cell_range_address:
2961            self.table_name = ""
2962            self.start = None
2963            self.end = None
2964            self.crange = None
2965            self.usage = None
2966            return
2967        self.usage = self.get_attribute("table:range-usable-as")
2968        name_range = cell_range_address.replace("$", "")
2969        name, crange = name_range.split(".", 1)
2970        if name.startswith("'") and name.endswith("'"):
2971            name = name[1:-1]
2972        self.table_name = name
2973        crange = crange.replace(".", "")
2974        self._set_range(crange)
2975
2976    def set_usage(self, usage: str | None = None) -> None:
2977        """Set the usage of the Named Range. Usage can be None (default) or one
2978        of :
2979            'print-range'
2980            'filter'
2981            'repeat-column'
2982            'repeat-row'
2983
2984        Arguments:
2985
2986            usage -- None or str
2987        """
2988        if usage is not None:
2989            usage = usage.strip().lower()
2990            if usage not in ("print-range", "filter", "repeat-column", "repeat-row"):
2991                usage = None
2992        if usage is None:
2993            with contextlib.suppress(KeyError):
2994                self.del_attribute("table:range-usable-as")
2995            self.usage = None
2996        else:
2997            self.set_attribute("table:range-usable-as", usage)
2998            self.usage = usage
2999
3000    @property
3001    def name(self) -> str | None:
3002        """Get / set the name of the table."""
3003        return self.get_attribute_string("table:name")
3004
3005    @name.setter
3006    def name(self, name: str) -> None:
3007        """Set the name of the Named Range. The name is mandatory, if a Named
3008        Range of the same name exists, it will be replaced. Name must contains
3009        only alphanumerics characters and '_', and can not be of a cell
3010        coordinates form like 'AB12'.
3011
3012        Arguments:
3013
3014            name -- str
3015        """
3016        name = name.strip()
3017        if not name:
3018            raise ValueError("Name required.")
3019        for x in name:
3020            if x in forbidden_in_named_range():
3021                raise ValueError(f"Character forbidden '{x}' ")
3022        step = ""
3023        for x in name:
3024            if x in string.ascii_letters and step in ("", "A"):
3025                step = "A"
3026                continue
3027            elif step in ("A", "A1") and x in string.digits:
3028                step = "A1"
3029                continue
3030            else:
3031                step = ""
3032                break
3033        if step == "A1":
3034            raise ValueError("Name of the type 'ABC123' is not allowed.")
3035        with contextlib.suppress(Exception):
3036            # we are not on an inserted in a document.
3037            body = self.document_body
3038            named_range = body.get_named_range(name)  # type: ignore
3039            if named_range:
3040                named_range.delete()
3041        self.set_attribute("table:name", name)
3042
3043    def set_table_name(self, name: str) -> None:
3044        """Set the name of the table of the Named Range. The name is mandatory.
3045
3046        Arguments:
3047
3048            name -- str
3049        """
3050        self.table_name = _table_name_check(name)
3051        self._update_attributes()
3052
3053    def _set_range(self, coord: tuple | list | str) -> None:
3054        digits = convert_coordinates(coord)
3055        if len(digits) == 4:
3056            x, y, z, t = digits
3057        else:
3058            x, y = digits
3059            z, t = digits
3060        self.start = x, y  # type: ignore
3061        self.end = z, t  # type: ignore
3062        self.crange = x, y, z, t  # type: ignore
3063
3064    def set_range(self, crange: str | tuple | list) -> None:
3065        """Set the range of the named range. Range can be either one cell
3066        (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric
3067        value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).
3068
3069        Arguments:
3070
3071            crange -- str or tuple of int, cell or area coordinate
3072        """
3073        self._set_range(crange)
3074        self._update_attributes()
3075
3076    def _update_attributes(self) -> None:
3077        self.set_attribute("table:base-cell-address", self._make_base_cell_address())
3078        self.set_attribute("table:cell-range-address", self._make_cell_range_address())
3079
3080    def _make_base_cell_address(self) -> str:
3081        # assuming we got table_name and range
3082        if " " in self.table_name:
3083            name = f"'{self.table_name}'"
3084        else:
3085            name = self.table_name
3086        return f"${name}.${digit_to_alpha(self.start[0])}${self.start[1] + 1}"  # type: ignore
3087
3088    def _make_cell_range_address(self) -> str:
3089        # assuming we got table_name and range
3090        if " " in self.table_name:
3091            name = f"'{self.table_name}'"
3092        else:
3093            name = self.table_name
3094        if self.start == self.end:
3095            return self._make_base_cell_address()
3096        return (
3097            f"${name}.${digit_to_alpha(self.start[0])}${self.start[1] + 1}:"  # type: ignore
3098            f".${digit_to_alpha(self.end[0])}${self.end[1] + 1}"  # type: ignore
3099        )
3100
3101    def get_values(
3102        self,
3103        cell_type: str | None = None,
3104        complete: bool = True,
3105        get_type: bool = False,
3106        flat: bool = False,
3107    ) -> list:
3108        """Shortcut to retrieve the values of the cells of the named range. See
3109        table.get_values() for the arguments description and return format.
3110        """
3111        body = self.document_body
3112        if not body:
3113            raise ValueError("Table is not inside a document.")
3114        table = body.get_table(name=self.table_name)
3115        if table is None:
3116            raise ValueError
3117        return table.get_values(self.crange, cell_type, complete, get_type, flat)  # type: ignore
3118
3119    def get_value(self, get_type: bool = False) -> Any:
3120        """Shortcut to retrieve the value of the first cell of the named range.
3121        See table.get_value() for the arguments description and return format.
3122        """
3123        body = self.document_body
3124        if not body:
3125            raise ValueError("Table is not inside a document.")
3126        table = body.get_table(name=self.table_name)
3127        if table is None:
3128            raise ValueError
3129        return table.get_value(self.start, get_type)  # type: ignore
3130
3131    def set_values(
3132        self,
3133        values: list,
3134        style: str | None = None,
3135        cell_type: str | None = None,
3136        currency: str | None = None,
3137    ) -> None:
3138        """Shortcut to set the values of the cells of the named range.
3139        See table.set_values() for the arguments description.
3140        """
3141        body = self.document_body
3142        if not body:
3143            raise ValueError("Table is not inside a document.")
3144        table = body.get_table(name=self.table_name)
3145        if table is None:
3146            raise ValueError
3147        table.set_values(  # type: ignore
3148            values,
3149            coord=self.crange,
3150            style=style,
3151            cell_type=cell_type,
3152            currency=currency,
3153        )
3154
3155    def set_value(
3156        self,
3157        value: Any,
3158        cell_type: str | None = None,
3159        currency: str | None = None,
3160        style: str | None = None,
3161    ) -> None:
3162        """Shortcut to set the value of the first cell of the named range.
3163        See table.set_value() for the arguments description.
3164        """
3165        body = self.document_body
3166        if not body:
3167            raise ValueError("Table is not inside a document.")
3168        table = body.get_table(name=self.table_name)
3169        if table is None:
3170            raise ValueError
3171        table.set_value(  # type: ignore
3172            coord=self.start,
3173            value=value,
3174            cell_type=cell_type,
3175            currency=currency,
3176            style=style,
3177        )

ODF Named Range "table:named-range". Identifies inside the spreadsheet a range of cells of a table by a name and the name of the table.

Name Ranges have the following attributes:

name -- name of the named range

table_name -- name of the table

start -- first cell of the named range, tuple (x, y)

end -- last cell of the named range, tuple (x, y)

crange -- range of the named range, tuple (x, y, z, t)

usage -- None or str, usage of the named range.
NamedRange( name: str | None = None, crange: str | tuple | list | None = None, table_name: str | None = None, usage: str | None = None, **kwargs: Any)
2930    def __init__(
2931        self,
2932        name: str | None = None,
2933        crange: str | tuple | list | None = None,
2934        table_name: str | None = None,
2935        usage: str | None = None,
2936        **kwargs: Any,
2937    ) -> None:
2938        """Create a Named Range element. 'name' must contains only letters, digits
2939           and '_', and must not be like a coordinate as 'A1'. 'table_name' must be
2940           a correct table name (no "'" or "/" in it).
2941
2942        Arguments:
2943
2944             name -- str, name of the named range
2945
2946             crange -- str or tuple of int, cell or area coordinate
2947
2948             table_name -- str, name of the table
2949
2950             usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2951        """
2952        super().__init__(**kwargs)
2953        self.usage = None
2954        if self._do_init:
2955            self.name = name or ""
2956            self.table_name = _table_name_check(table_name)
2957            self.set_range(crange or "")
2958            self.set_usage(usage)
2959        cell_range_address = self.get_attribute_string("table:cell-range-address") or ""
2960        if not cell_range_address:
2961            self.table_name = ""
2962            self.start = None
2963            self.end = None
2964            self.crange = None
2965            self.usage = None
2966            return
2967        self.usage = self.get_attribute("table:range-usable-as")
2968        name_range = cell_range_address.replace("$", "")
2969        name, crange = name_range.split(".", 1)
2970        if name.startswith("'") and name.endswith("'"):
2971            name = name[1:-1]
2972        self.table_name = name
2973        crange = crange.replace(".", "")
2974        self._set_range(crange)

Create a Named Range element. 'name' must contains only letters, digits and '_', and must not be like a coordinate as 'A1'. 'table_name' must be a correct table name (no "'" or "/" in it).

Arguments:

 name -- str, name of the named range

 crange -- str or tuple of int, cell or area coordinate

 table_name -- str, name of the table

 usage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
usage
table_name
def set_usage(self, usage: str | None = None) -> None:
2976    def set_usage(self, usage: str | None = None) -> None:
2977        """Set the usage of the Named Range. Usage can be None (default) or one
2978        of :
2979            'print-range'
2980            'filter'
2981            'repeat-column'
2982            'repeat-row'
2983
2984        Arguments:
2985
2986            usage -- None or str
2987        """
2988        if usage is not None:
2989            usage = usage.strip().lower()
2990            if usage not in ("print-range", "filter", "repeat-column", "repeat-row"):
2991                usage = None
2992        if usage is None:
2993            with contextlib.suppress(KeyError):
2994                self.del_attribute("table:range-usable-as")
2995            self.usage = None
2996        else:
2997            self.set_attribute("table:range-usable-as", usage)
2998            self.usage = usage

Set the usage of the Named Range. Usage can be None (default) or one of : 'print-range' 'filter' 'repeat-column' 'repeat-row'

Arguments:

usage -- None or str
name: str | None
3000    @property
3001    def name(self) -> str | None:
3002        """Get / set the name of the table."""
3003        return self.get_attribute_string("table:name")

Get / set the name of the table.

def set_table_name(self, name: str) -> None:
3043    def set_table_name(self, name: str) -> None:
3044        """Set the name of the table of the Named Range. The name is mandatory.
3045
3046        Arguments:
3047
3048            name -- str
3049        """
3050        self.table_name = _table_name_check(name)
3051        self._update_attributes()

Set the name of the table of the Named Range. The name is mandatory.

Arguments:

name -- str
def set_range(self, crange: str | tuple | list) -> None:
3064    def set_range(self, crange: str | tuple | list) -> None:
3065        """Set the range of the named range. Range can be either one cell
3066        (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric
3067        value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).
3068
3069        Arguments:
3070
3071            crange -- str or tuple of int, cell or area coordinate
3072        """
3073        self._set_range(crange)
3074        self._update_attributes()

Set the range of the named range. Range can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).

Arguments:

crange -- str or tuple of int, cell or area coordinate
def get_values( self, cell_type: str | None = None, complete: bool = True, get_type: bool = False, flat: bool = False) -> list:
3101    def get_values(
3102        self,
3103        cell_type: str | None = None,
3104        complete: bool = True,
3105        get_type: bool = False,
3106        flat: bool = False,
3107    ) -> list:
3108        """Shortcut to retrieve the values of the cells of the named range. See
3109        table.get_values() for the arguments description and return format.
3110        """
3111        body = self.document_body
3112        if not body:
3113            raise ValueError("Table is not inside a document.")
3114        table = body.get_table(name=self.table_name)
3115        if table is None:
3116            raise ValueError
3117        return table.get_values(self.crange, cell_type, complete, get_type, flat)  # type: ignore

Shortcut to retrieve the values of the cells of the named range. See table.get_values() for the arguments description and return format.

def get_value(self, get_type: bool = False) -> Any:
3119    def get_value(self, get_type: bool = False) -> Any:
3120        """Shortcut to retrieve the value of the first cell of the named range.
3121        See table.get_value() for the arguments description and return format.
3122        """
3123        body = self.document_body
3124        if not body:
3125            raise ValueError("Table is not inside a document.")
3126        table = body.get_table(name=self.table_name)
3127        if table is None:
3128            raise ValueError
3129        return table.get_value(self.start, get_type)  # type: ignore

Shortcut to retrieve the value of the first cell of the named range. See table.get_value() for the arguments description and return format.

def set_values( self, values: list, style: str | None = None, cell_type: str | None = None, currency: str | None = None) -> None:
3131    def set_values(
3132        self,
3133        values: list,
3134        style: str | None = None,
3135        cell_type: str | None = None,
3136        currency: str | None = None,
3137    ) -> None:
3138        """Shortcut to set the values of the cells of the named range.
3139        See table.set_values() for the arguments description.
3140        """
3141        body = self.document_body
3142        if not body:
3143            raise ValueError("Table is not inside a document.")
3144        table = body.get_table(name=self.table_name)
3145        if table is None:
3146            raise ValueError
3147        table.set_values(  # type: ignore
3148            values,
3149            coord=self.crange,
3150            style=style,
3151            cell_type=cell_type,
3152            currency=currency,
3153        )

Shortcut to set the values of the cells of the named range. See table.set_values() for the arguments description.

def set_value( self, value: Any, cell_type: str | None = None, currency: str | None = None, style: str | None = None) -> None:
3155    def set_value(
3156        self,
3157        value: Any,
3158        cell_type: str | None = None,
3159        currency: str | None = None,
3160        style: str | None = None,
3161    ) -> None:
3162        """Shortcut to set the value of the first cell of the named range.
3163        See table.set_value() for the arguments description.
3164        """
3165        body = self.document_body
3166        if not body:
3167            raise ValueError("Table is not inside a document.")
3168        table = body.get_table(name=self.table_name)
3169        if table is None:
3170            raise ValueError
3171        table.set_value(  # type: ignore
3172            coord=self.start,
3173            value=value,
3174            cell_type=cell_type,
3175            currency=currency,
3176            style=style,
3177        )

Shortcut to set the value of the first cell of the named range. See table.set_value() for the arguments description.

Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Note(odfdo.Element):
 57class Note(Element):
 58    """Either a footnote or a endnote element with the given text,
 59    optionally referencing it using the given note_id.
 60
 61    Arguments:
 62
 63        note_class -- 'footnote' or 'endnote'
 64
 65        note_id -- str
 66
 67        citation -- str
 68
 69        body -- str or Element
 70    """
 71
 72    _tag = "text:note"
 73    _properties = (
 74        PropDef("note_class", "text:note-class"),
 75        PropDef("note_id", "text:id"),
 76    )
 77
 78    def __init__(
 79        self,
 80        note_class: str = "footnote",
 81        note_id: str | None = None,
 82        citation: str | None = None,
 83        body: str | None = None,
 84        **kwargs: Any,
 85    ) -> None:
 86        super().__init__(**kwargs)
 87        if self._do_init:
 88            self.insert(Element.from_tag("text:note-body"), position=0)
 89            self.insert(Element.from_tag("text:note-citation"), position=0)
 90            self.note_class = note_class
 91            if note_id is not None:
 92                self.note_id = note_id
 93            if citation is not None:
 94                self.citation = citation
 95            if body is not None:
 96                self.note_body = body
 97
 98    @property
 99    def citation(self) -> str:
100        note_citation = self.get_element("text:note-citation")
101        if note_citation:
102            return note_citation.text
103        return ""
104
105    @citation.setter
106    def citation(self, text: str | None) -> None:
107        note_citation = self.get_element("text:note-citation")
108        if note_citation:
109            note_citation.text = text  # type:ignore
110
111    @property
112    def note_body(self) -> str:
113        note_body = self.get_element("text:note-body")
114        if note_body:
115            return note_body.text_content
116        return ""
117
118    @note_body.setter
119    def note_body(self, text_or_element: Element | str | None) -> None:
120        note_body = self.get_element("text:note-body")
121        if not note_body:
122            return None
123        if text_or_element is None:
124            note_body.text_content = ""
125        elif isinstance(text_or_element, str):
126            note_body.text_content = text_or_element
127        elif isinstance(text_or_element, Element):
128            note_body.clear()
129            note_body.append(text_or_element)
130        else:
131            raise TypeError(f'Unexpected type for body: "{type(text_or_element)}"')
132
133    def check_validity(self) -> None:
134        if not self.note_class:
135            raise ValueError('Note class must be "footnote" or "endnote"')
136        if not self.note_id:
137            raise ValueError("Note must have an id")
138        if not self.citation:
139            raise ValueError("Note must have a citation")
140        if not self.note_body:
141            pass

Either a footnote or a endnote element with the given text, optionally referencing it using the given note_id.

Arguments:

note_class -- 'footnote' or 'endnote'

note_id -- str

citation -- str

body -- str or Element
Note( note_class: str = 'footnote', note_id: str | None = None, citation: str | None = None, body: str | None = None, **kwargs: Any)
78    def __init__(
79        self,
80        note_class: str = "footnote",
81        note_id: str | None = None,
82        citation: str | None = None,
83        body: str | None = None,
84        **kwargs: Any,
85    ) -> None:
86        super().__init__(**kwargs)
87        if self._do_init:
88            self.insert(Element.from_tag("text:note-body"), position=0)
89            self.insert(Element.from_tag("text:note-citation"), position=0)
90            self.note_class = note_class
91            if note_id is not None:
92                self.note_id = note_id
93            if citation is not None:
94                self.citation = citation
95            if body is not None:
96                self.note_body = body
citation: str
 98    @property
 99    def citation(self) -> str:
100        note_citation = self.get_element("text:note-citation")
101        if note_citation:
102            return note_citation.text
103        return ""
note_body: str
111    @property
112    def note_body(self) -> str:
113        note_body = self.get_element("text:note-body")
114        if note_body:
115            return note_body.text_content
116        return ""
def check_validity(self) -> None:
133    def check_validity(self) -> None:
134        if not self.note_class:
135            raise ValueError('Note class must be "footnote" or "endnote"')
136        if not self.note_id:
137            raise ValueError("Note must have an id")
138        if not self.citation:
139            raise ValueError("Note must have a citation")
140        if not self.note_body:
141            pass
note_class: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
note_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
PREV_SIBLING = 3
def PageBreak() -> Paragraph:
831def PageBreak() -> Paragraph:
832    """Return an empty paragraph with a manual page break.
833
834    Using this function requires to register the page break style with:
835        document.add_page_break_style()
836    """
837    return Paragraph("", style="odfdopagebreak")

Return an empty paragraph with a manual page break.

Using this function requires to register the page break style with: document.add_page_break_style()

class Paragraph(odfdo.paragraph_base.ParagraphBase):
147class Paragraph(ParagraphBase):
148    """Specialised element for paragraphs "text:p". The "text:p" element
149    represents a paragraph, which is the basic unit of text in an OpenDocument
150    file.
151    """
152
153    _tag = "text:p"
154
155    def __init__(
156        self,
157        text_or_element: str | Element | None = None,
158        style: str | None = None,
159        **kwargs: Any,
160    ):
161        """Create a paragraph element of the given style containing the optional
162        given text.
163
164        Arguments:
165
166            text -- str or Element
167
168            style -- str
169        """
170        super().__init__(**kwargs)
171        if self._do_init:
172            if isinstance(text_or_element, Element):
173                self.append(text_or_element)
174            else:
175                self.text = text_or_element  # type:ignore
176            if style is not None:
177                self.style = style
178
179    def insert_note(
180        self,
181        note_element: Note | None = None,
182        after: str | Element | None = None,
183        note_class: str = "footnote",
184        note_id: str | None = None,
185        citation: str | None = None,
186        body: str | None = None,
187    ) -> None:
188        if note_element is None:
189            note_element = Note(
190                note_class=note_class, note_id=note_id, citation=citation, body=body
191            )
192        else:
193            # XXX clone or modify the argument?
194            if note_class:
195                note_element.note_class = note_class
196            if note_id:
197                note_element.note_id = note_id
198            if citation:
199                note_element.citation = citation
200            if body:
201                note_element.note_body = body
202        note_element.check_validity()
203        if isinstance(after, str):
204            self._insert(note_element, after=after, main_text=True)
205        elif isinstance(after, Element):
206            after.insert(note_element, FIRST_CHILD)
207        else:
208            self.insert(note_element, FIRST_CHILD)
209
210    def insert_annotation(  # noqa: C901
211        self,
212        annotation_element: Annotation | None = None,
213        before: str | None = None,
214        after: str | Element | None = None,
215        position: int | tuple = 0,
216        content: str | Element | None = None,
217        body: str | None = None,
218        creator: str | None = None,
219        date: datetime | None = None,
220    ) -> Annotation:
221        """Insert an annotation, at the position defined by the regex (before,
222        after, content) or by positionnal argument (position). If content is
223        provided, the annotation covers the full content regex. Else, the
224        annotation is positionned either 'before' or 'after' provided regex.
225
226        If content is an odf element (ie: paragraph, span, ...), the full inner
227        content is covered by the annotation (of the position just after if
228        content is a single empty tag).
229
230        If content/before or after exists (regex) and return a group of matching
231        positions, the position value is the index of matching place to use.
232
233        annotation_element can contain a previously created annotation, else
234        the annotation is created from the body, creator and optional date
235        (current date by default).
236
237        Arguments:
238
239            annotation_element -- Annotation or None
240
241            before -- str regular expression or None
242
243            after -- str regular expression or Element or None
244
245            content -- str regular expression or None, or Element
246
247            position -- int or tuple of int
248
249            body -- str or Element
250
251            creator -- str
252
253            date -- datetime
254        """
255
256        if annotation_element is None:
257            annotation_element = Annotation(
258                text_or_element=body, creator=creator, date=date, parent=self
259            )
260        else:
261            # XXX clone or modify the argument?
262            if body:
263                annotation_element.note_body = body
264            if creator:
265                annotation_element.dc_creator = creator
266            if date:
267                annotation_element.dc_date = date
268        annotation_element.check_validity()
269
270        # special case: content is an odf element (ie: a paragraph)
271        if isinstance(content, Element):
272            if content.is_empty():
273                content.insert(annotation_element, xmlposition=NEXT_SIBLING)
274                return annotation_element
275            content.insert(annotation_element, start=True)
276            annotation_end = AnnotationEnd(annotation_element)
277            content.append(annotation_end)
278            return annotation_element
279
280        # special case
281        if isinstance(after, Element):
282            after.insert(annotation_element, FIRST_CHILD)
283            return annotation_element
284
285        # With "content" => automatically insert a "start" and an "end"
286        # bookmark
287        if (
288            before is None
289            and after is None
290            and content is not None
291            and isinstance(position, int)
292        ):
293            # Start tag
294            self._insert(
295                annotation_element, before=content, position=position, main_text=True
296            )
297            # End tag
298            annotation_end = AnnotationEnd(annotation_element)
299            self._insert(
300                annotation_end, after=content, position=position, main_text=True
301            )
302            return annotation_element
303
304        # With "(int, int)" =>  automatically insert a "start" and an "end"
305        # bookmark
306        if (
307            before is None
308            and after is None
309            and content is None
310            and isinstance(position, tuple)
311        ):
312            # Start
313            self._insert(annotation_element, position=position[0], main_text=True)
314            # End
315            annotation_end = AnnotationEnd(annotation_element)
316            self._insert(annotation_end, position=position[1], main_text=True)
317            return annotation_element
318
319        # Without "content" nor "position"
320        if content is not None or not isinstance(position, int):
321            raise ValueError("Bad arguments")
322
323        # Insert
324        self._insert(
325            annotation_element,
326            before=before,
327            after=after,
328            position=position,
329            main_text=True,
330        )
331        return annotation_element
332
333    def insert_annotation_end(
334        self,
335        annotation_element: Annotation,
336        before: str | None = None,
337        after: str | None = None,
338        position: int = 0,
339    ) -> AnnotationEnd:
340        """Insert an annotation end tag for an existing annotation. If some end
341        tag already exists, replace it. Annotation end tag is set at the
342        position defined by the regex (before or after).
343
344        If content/before or after (regex) returns a group of matching
345        positions, the position value is the index of matching place to use.
346
347        Arguments:
348
349            annotation_element -- Annotation (mandatory)
350
351            before -- str regular expression or None
352
353            after -- str regular expression or None
354
355            position -- int
356        """
357
358        if annotation_element is None:
359            raise ValueError
360        if not isinstance(annotation_element, Annotation):
361            raise TypeError("Not a <office:annotation> Annotation")
362
363        # remove existing end tag
364        name = annotation_element.name
365        existing_end_tag = self.get_annotation_end(name=name)
366        if existing_end_tag:
367            existing_end_tag.delete()
368
369        # create the end tag
370        end_tag = AnnotationEnd(annotation_element)
371
372        # Insert
373        self._insert(
374            end_tag, before=before, after=after, position=position, main_text=True
375        )
376        return end_tag
377
378    def set_reference_mark(
379        self,
380        name: str,
381        before: str | None = None,
382        after: str | None = None,
383        position: int = 0,
384        content: str | Element | None = None,
385    ) -> Element:
386        """Insert a reference mark, at the position defined by the regex
387        (before, after, content) or by positionnal argument (position). If
388        content is provided, the annotation covers the full range content regex
389        (instances of ReferenceMarkStart and ReferenceMarkEnd are
390        created). Else, an instance of ReferenceMark is positionned either
391        'before' or 'after' provided regex.
392
393        If content is an ODF Element (ie: Paragraph, Span, ...), the full inner
394        content is referenced (of the position just after if content is a single
395        empty tag).
396
397        If content/before or after exists (regex) and return a group of matching
398        positions, the position value is the index of matching place to use.
399
400        Name is mandatory and shall be unique in the document for the preference
401        mark range.
402
403        Arguments:
404
405            name -- str
406
407            before -- str regular expression or None
408
409            after -- str regular expression or None,
410
411            content -- str regular expression or None, or Element
412
413            position -- int or tuple of int
414
415        Return: the created ReferenceMark or ReferenceMarkStart
416        """
417        # special case: content is an odf element (ie: a paragraph)
418        if isinstance(content, Element):
419            if content.is_empty():
420                reference = ReferenceMark(name)
421                content.insert(reference, xmlposition=NEXT_SIBLING)
422                return reference
423            reference_start = ReferenceMarkStart(name)
424            content.insert(reference_start, start=True)
425            reference_end = ReferenceMarkEnd(name)
426            content.append(reference_end)
427            return reference_start
428
429        # With "content" => automatically insert a "start" and an "end"
430        # reference
431        if (
432            before is None
433            and after is None
434            and content is not None
435            and isinstance(position, int)
436        ):
437            # Start tag
438            reference_start = ReferenceMarkStart(name)
439            self._insert(
440                reference_start, before=content, position=position, main_text=True
441            )
442            # End tag
443            reference_end = ReferenceMarkEnd(name)
444            self._insert(
445                reference_end, after=content, position=position, main_text=True
446            )
447            return reference_start
448
449        # With "(int, int)" =>  automatically insert a "start" and an "end"
450        if (
451            before is None
452            and after is None
453            and content is None
454            and isinstance(position, tuple)
455        ):
456            # Start
457            reference_start = ReferenceMarkStart(name)
458            self._insert(reference_start, position=position[0], main_text=True)
459            # End
460            reference_end = ReferenceMarkEnd(name)
461            self._insert(reference_end, position=position[1], main_text=True)
462            return reference_start
463
464        # Without "content" nor "position"
465        if content is not None or not isinstance(position, int):
466            raise ValueError("bad arguments")
467
468        # Insert a positional reference mark
469        reference = ReferenceMark(name)
470        self._insert(
471            reference,
472            before=before,
473            after=after,
474            position=position,
475            main_text=True,
476        )
477        return reference
478
479    def set_reference_mark_end(
480        self,
481        reference_mark: Element,
482        before: str | None = None,
483        after: str | None = None,
484        position: int = 0,
485    ) -> ReferenceMarkEnd:
486        """Insert/move a ReferenceMarkEnd for an existing reference mark. If
487        some end tag already exists, replace it. Reference tag is set at the
488        position defined by the regex (before or after).
489
490        If content/before or after (regex) returns a group of matching
491        positions, the position value is the index of matching place to use.
492
493        Arguments:
494
495            reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory)
496
497            before -- str regular expression or None
498
499            after -- str regular expression or None
500
501            position -- int
502        """
503        if not isinstance(reference_mark, (ReferenceMark, ReferenceMarkStart)):
504            raise TypeError("Not a ReferenceMark or ReferenceMarkStart")
505        name = reference_mark.name
506        if isinstance(reference_mark, ReferenceMark):
507            # change it to a range reference:
508            reference_mark.tag = ReferenceMarkStart._tag
509
510        existing_end_tag = self.get_reference_mark_end(name=name)
511        if existing_end_tag:
512            existing_end_tag.delete()
513
514        # create the end tag
515        end_tag = ReferenceMarkEnd(name)
516
517        # Insert
518        self._insert(
519            end_tag, before=before, after=after, position=position, main_text=True
520        )
521        return end_tag
522
523    def insert_variable(self, variable_element: Element, after: str | None) -> None:
524        self._insert(variable_element, after=after, main_text=True)
525
526    @_by_regex_offset
527    def set_span(
528        self,
529        match: str,
530        tail: str,
531        style: str,
532        regex: str | None = None,
533        offset: int | None = None,
534        length: int = 0,
535    ) -> Span:
536        """
537        set_span(style, regex=None, offset=None, length=0)
538        Apply the given style to text content matching the regex OR the
539        positional arguments offset and length.
540
541        (match, tail: provided by regex decorator)
542
543        Arguments:
544
545            style -- str
546
547            regex -- str regular expression
548
549            offset -- int
550
551            length -- int
552        """
553        span = Span(match, style=style)
554        span.tail = tail
555        return span
556
557    def remove_spans(self, keep_heading: bool = True) -> Element | list:
558        """Send back a copy of the element, without span styles.
559        If keep_heading is True (default), the first level heading style is left
560        unchanged.
561        """
562        strip = (Span._tag,)
563        if keep_heading:
564            protect = ("text:h",)
565        else:
566            protect = None
567        return self.strip_tags(strip=strip, protect=protect)
568
569    def remove_span(self, spans: Element | list[Element]) -> Element | list:
570        """Send back a copy of the element, the spans (not a clone) removed.
571
572        Arguments:
573
574            spans -- Element or list of Element
575        """
576        return self.strip_elements(spans)
577
578    @_by_regex_offset
579    def set_link(
580        self,
581        match: str,
582        tail: str,
583        url: str,
584        regex: str | None = None,
585        offset: int | None = None,
586        length: int = 0,
587    ) -> Element:
588        """
589        set_link(url, regex=None, offset=None, length=0)
590        Make a link to the provided url from text content matching the regex
591        OR the positional arguments offset and length.
592
593        (match, tail: provided by regex decorator)
594
595        Arguments:
596
597            url -- str
598
599            regex -- str regular expression
600
601            offset -- int
602
603            length -- int
604        """
605        link = Link(url, text=match)
606        link.tail = tail
607        return link
608
609    def remove_links(self) -> Element | list:
610        """Send back a copy of the element, without links tags."""
611        strip = (Link._tag,)
612        return self.strip_tags(strip=strip)
613
614    def remove_link(self, links: Link | list[Link]) -> Element | list:
615        """Send back a copy of the element (not a clone), with the sub links
616           removed.
617
618        Arguments:
619
620            links -- Link or list of Link
621        """
622        return self.strip_elements(links)
623
624    def insert_reference(
625        self,
626        name: str,
627        ref_format: str = "",
628        before: str | None = None,
629        after: str | Element | None = None,
630        position: int = 0,
631        display: str | None = None,
632    ) -> None:
633        """Create and insert a reference to a content marked by a reference
634        mark. The Reference element ("text:reference-ref") represents a
635        field that references a "text:reference-mark-start" or
636        "text:reference-mark" element. Its "text:reference-format" attribute
637        specifies what is displayed from the referenced element. Default is
638        'page'. Actual content is not automatically updated except for the 'text'
639        format.
640
641        name is mandatory and should represent an existing reference mark of the
642        document.
643
644        ref_format is the argument for format reference (default is 'page').
645
646        The reference is inserted the position defined by the regex (before /
647        after), or by positionnal argument (position). If 'display' is provided,
648        it will be used as the text value for the reference.
649
650        If after is an ODF Element, the reference is inserted as first child of
651        this element.
652
653        Arguments:
654
655            name -- str
656
657            ref_format -- one of : 'chapter', 'direction', 'page', 'text',
658                                    'caption', 'category-and-value', 'value',
659                                    'number', 'number-all-superior',
660                                    'number-no-superior'
661
662            before -- str regular expression or None
663
664            after -- str regular expression or odf element or None
665
666            position -- int
667
668            display -- str or None
669        """
670        reference = Reference(name, ref_format)
671        if display is None and ref_format == "text":
672            # get reference content
673            body = self.document_body
674            if not body:
675                body = self.root
676            mark = body.get_reference_mark(name=name)
677            if mark:
678                display = mark.referenced_text  # type: ignore
679        if not display:
680            display = " "
681        reference.text = display
682        if isinstance(after, Element):
683            after.insert(reference, FIRST_CHILD)
684        else:
685            self._insert(
686                reference, before=before, after=after, position=position, main_text=True
687            )
688
689    def set_bookmark(
690        self,
691        name: str,
692        before: str | None = None,
693        after: str | None = None,
694        position: int | tuple = 0,
695        role: str | None = None,
696        content: str | None = None,
697    ) -> Element | tuple[Element, Element]:
698        """Insert a bookmark before or after the characters in the text which
699        match the regex before/after. When the regex matches more of one part
700        of the text, position can be set to choose which part must be used.
701        If before and after are None, we use only position that is the number
702        of characters.
703
704        So, by default, this function inserts a bookmark before the first
705        character of the content. Role can be None, "start" or "end", we
706        insert respectively a position bookmark a bookmark-start or a
707        bookmark-end.
708
709        If content is not None these 2 calls are equivalent:
710
711          paragraph.set_bookmark("bookmark", content="xyz")
712
713        and:
714
715          paragraph.set_bookmark("bookmark", before="xyz", role="start")
716          paragraph.set_bookmark("bookmark", after="xyz", role="end")
717
718
719        If position is a 2-tuple, these 2 calls are equivalent:
720
721          paragraph.set_bookmark("bookmark", position=(10, 20))
722
723        and:
724
725          paragraph.set_bookmark("bookmark", position=10, role="start")
726          paragraph.set_bookmark("bookmark", position=20, role="end")
727
728
729        Arguments:
730
731            name -- str
732
733            before -- str regex
734
735            after -- str regex
736
737            position -- int or (int, int)
738
739            role -- None, "start" or "end"
740
741            content -- str regex
742        """
743        # With "content" => automatically insert a "start" and an "end"
744        # bookmark
745        if (
746            before is None
747            and after is None
748            and role is None
749            and content is not None
750            and isinstance(position, int)
751        ):
752            # Start
753            start = BookmarkStart(name)
754            self._insert(start, before=content, position=position, main_text=True)
755            # End
756            end = BookmarkEnd(name)
757            self._insert(end, after=content, position=position, main_text=True)
758            return start, end
759
760        # With "(int, int)" =>  automatically insert a "start" and an "end"
761        # bookmark
762        if (
763            before is None
764            and after is None
765            and role is None
766            and content is None
767            and isinstance(position, tuple)
768        ):
769            # Start
770            start = BookmarkStart(name)
771            self._insert(start, position=position[0], main_text=True)
772            # End
773            end = BookmarkEnd(name)
774            self._insert(end, position=position[1], main_text=True)
775            return start, end
776
777        # Without "content" nor "position"
778        if content is not None or not isinstance(position, int):
779            raise ValueError("bad arguments")
780
781        # Role
782        if role is None:
783            bookmark: Element = Bookmark(name)
784        elif role == "start":
785            bookmark = BookmarkStart(name)
786        elif role == "end":
787            bookmark = BookmarkEnd(name)
788        else:
789            raise ValueError("bad arguments")
790
791        # Insert
792        self._insert(
793            bookmark, before=before, after=after, position=position, main_text=True
794        )
795
796        return bookmark

Specialised element for paragraphs "text:p". The "text:p" element represents a paragraph, which is the basic unit of text in an OpenDocument file.

Paragraph( text_or_element: str | Element | None = None, style: str | None = None, **kwargs: Any)
155    def __init__(
156        self,
157        text_or_element: str | Element | None = None,
158        style: str | None = None,
159        **kwargs: Any,
160    ):
161        """Create a paragraph element of the given style containing the optional
162        given text.
163
164        Arguments:
165
166            text -- str or Element
167
168            style -- str
169        """
170        super().__init__(**kwargs)
171        if self._do_init:
172            if isinstance(text_or_element, Element):
173                self.append(text_or_element)
174            else:
175                self.text = text_or_element  # type:ignore
176            if style is not None:
177                self.style = style

Create a paragraph element of the given style containing the optional given text.

Arguments:

text -- str or Element

style -- str
def insert_note( self, note_element: Note | None = None, after: str | Element | None = None, note_class: str = 'footnote', note_id: str | None = None, citation: str | None = None, body: str | None = None) -> None:
179    def insert_note(
180        self,
181        note_element: Note | None = None,
182        after: str | Element | None = None,
183        note_class: str = "footnote",
184        note_id: str | None = None,
185        citation: str | None = None,
186        body: str | None = None,
187    ) -> None:
188        if note_element is None:
189            note_element = Note(
190                note_class=note_class, note_id=note_id, citation=citation, body=body
191            )
192        else:
193            # XXX clone or modify the argument?
194            if note_class:
195                note_element.note_class = note_class
196            if note_id:
197                note_element.note_id = note_id
198            if citation:
199                note_element.citation = citation
200            if body:
201                note_element.note_body = body
202        note_element.check_validity()
203        if isinstance(after, str):
204            self._insert(note_element, after=after, main_text=True)
205        elif isinstance(after, Element):
206            after.insert(note_element, FIRST_CHILD)
207        else:
208            self.insert(note_element, FIRST_CHILD)
def insert_annotation( self, annotation_element: Annotation | None = None, before: str | None = None, after: str | Element | None = None, position: int | tuple = 0, content: str | Element | None = None, body: str | None = None, creator: str | None = None, date: datetime.datetime | None = None) -> Annotation:
210    def insert_annotation(  # noqa: C901
211        self,
212        annotation_element: Annotation | None = None,
213        before: str | None = None,
214        after: str | Element | None = None,
215        position: int | tuple = 0,
216        content: str | Element | None = None,
217        body: str | None = None,
218        creator: str | None = None,
219        date: datetime | None = None,
220    ) -> Annotation:
221        """Insert an annotation, at the position defined by the regex (before,
222        after, content) or by positionnal argument (position). If content is
223        provided, the annotation covers the full content regex. Else, the
224        annotation is positionned either 'before' or 'after' provided regex.
225
226        If content is an odf element (ie: paragraph, span, ...), the full inner
227        content is covered by the annotation (of the position just after if
228        content is a single empty tag).
229
230        If content/before or after exists (regex) and return a group of matching
231        positions, the position value is the index of matching place to use.
232
233        annotation_element can contain a previously created annotation, else
234        the annotation is created from the body, creator and optional date
235        (current date by default).
236
237        Arguments:
238
239            annotation_element -- Annotation or None
240
241            before -- str regular expression or None
242
243            after -- str regular expression or Element or None
244
245            content -- str regular expression or None, or Element
246
247            position -- int or tuple of int
248
249            body -- str or Element
250
251            creator -- str
252
253            date -- datetime
254        """
255
256        if annotation_element is None:
257            annotation_element = Annotation(
258                text_or_element=body, creator=creator, date=date, parent=self
259            )
260        else:
261            # XXX clone or modify the argument?
262            if body:
263                annotation_element.note_body = body
264            if creator:
265                annotation_element.dc_creator = creator
266            if date:
267                annotation_element.dc_date = date
268        annotation_element.check_validity()
269
270        # special case: content is an odf element (ie: a paragraph)
271        if isinstance(content, Element):
272            if content.is_empty():
273                content.insert(annotation_element, xmlposition=NEXT_SIBLING)
274                return annotation_element
275            content.insert(annotation_element, start=True)
276            annotation_end = AnnotationEnd(annotation_element)
277            content.append(annotation_end)
278            return annotation_element
279
280        # special case
281        if isinstance(after, Element):
282            after.insert(annotation_element, FIRST_CHILD)
283            return annotation_element
284
285        # With "content" => automatically insert a "start" and an "end"
286        # bookmark
287        if (
288            before is None
289            and after is None
290            and content is not None
291            and isinstance(position, int)
292        ):
293            # Start tag
294            self._insert(
295                annotation_element, before=content, position=position, main_text=True
296            )
297            # End tag
298            annotation_end = AnnotationEnd(annotation_element)
299            self._insert(
300                annotation_end, after=content, position=position, main_text=True
301            )
302            return annotation_element
303
304        # With "(int, int)" =>  automatically insert a "start" and an "end"
305        # bookmark
306        if (
307            before is None
308            and after is None
309            and content is None
310            and isinstance(position, tuple)
311        ):
312            # Start
313            self._insert(annotation_element, position=position[0], main_text=True)
314            # End
315            annotation_end = AnnotationEnd(annotation_element)
316            self._insert(annotation_end, position=position[1], main_text=True)
317            return annotation_element
318
319        # Without "content" nor "position"
320        if content is not None or not isinstance(position, int):
321            raise ValueError("Bad arguments")
322
323        # Insert
324        self._insert(
325            annotation_element,
326            before=before,
327            after=after,
328            position=position,
329            main_text=True,
330        )
331        return annotation_element

Insert an annotation, at the position defined by the regex (before, after, content) or by positionnal argument (position). If content is provided, the annotation covers the full content regex. Else, the annotation is positionned either 'before' or 'after' provided regex.

If content is an odf element (ie: paragraph, span, ...), the full inner content is covered by the annotation (of the position just after if content is a single empty tag).

If content/before or after exists (regex) and return a group of matching positions, the position value is the index of matching place to use.

annotation_element can contain a previously created annotation, else the annotation is created from the body, creator and optional date (current date by default).

Arguments:

annotation_element -- Annotation or None

before -- str regular expression or None

after -- str regular expression or Element or None

content -- str regular expression or None, or Element

position -- int or tuple of int

body -- str or Element

creator -- str

date -- datetime
def insert_annotation_end( self, annotation_element: Annotation, before: str | None = None, after: str | None = None, position: int = 0) -> AnnotationEnd:
333    def insert_annotation_end(
334        self,
335        annotation_element: Annotation,
336        before: str | None = None,
337        after: str | None = None,
338        position: int = 0,
339    ) -> AnnotationEnd:
340        """Insert an annotation end tag for an existing annotation. If some end
341        tag already exists, replace it. Annotation end tag is set at the
342        position defined by the regex (before or after).
343
344        If content/before or after (regex) returns a group of matching
345        positions, the position value is the index of matching place to use.
346
347        Arguments:
348
349            annotation_element -- Annotation (mandatory)
350
351            before -- str regular expression or None
352
353            after -- str regular expression or None
354
355            position -- int
356        """
357
358        if annotation_element is None:
359            raise ValueError
360        if not isinstance(annotation_element, Annotation):
361            raise TypeError("Not a <office:annotation> Annotation")
362
363        # remove existing end tag
364        name = annotation_element.name
365        existing_end_tag = self.get_annotation_end(name=name)
366        if existing_end_tag:
367            existing_end_tag.delete()
368
369        # create the end tag
370        end_tag = AnnotationEnd(annotation_element)
371
372        # Insert
373        self._insert(
374            end_tag, before=before, after=after, position=position, main_text=True
375        )
376        return end_tag

Insert an annotation end tag for an existing annotation. If some end tag already exists, replace it. Annotation end tag is set at the position defined by the regex (before or after).

If content/before or after (regex) returns a group of matching positions, the position value is the index of matching place to use.

Arguments:

annotation_element -- Annotation (mandatory)

before -- str regular expression or None

after -- str regular expression or None

position -- int
def set_reference_mark( self, name: str, before: str | None = None, after: str | None = None, position: int = 0, content: str | Element | None = None) -> Element:
378    def set_reference_mark(
379        self,
380        name: str,
381        before: str | None = None,
382        after: str | None = None,
383        position: int = 0,
384        content: str | Element | None = None,
385    ) -> Element:
386        """Insert a reference mark, at the position defined by the regex
387        (before, after, content) or by positionnal argument (position). If
388        content is provided, the annotation covers the full range content regex
389        (instances of ReferenceMarkStart and ReferenceMarkEnd are
390        created). Else, an instance of ReferenceMark is positionned either
391        'before' or 'after' provided regex.
392
393        If content is an ODF Element (ie: Paragraph, Span, ...), the full inner
394        content is referenced (of the position just after if content is a single
395        empty tag).
396
397        If content/before or after exists (regex) and return a group of matching
398        positions, the position value is the index of matching place to use.
399
400        Name is mandatory and shall be unique in the document for the preference
401        mark range.
402
403        Arguments:
404
405            name -- str
406
407            before -- str regular expression or None
408
409            after -- str regular expression or None,
410
411            content -- str regular expression or None, or Element
412
413            position -- int or tuple of int
414
415        Return: the created ReferenceMark or ReferenceMarkStart
416        """
417        # special case: content is an odf element (ie: a paragraph)
418        if isinstance(content, Element):
419            if content.is_empty():
420                reference = ReferenceMark(name)
421                content.insert(reference, xmlposition=NEXT_SIBLING)
422                return reference
423            reference_start = ReferenceMarkStart(name)
424            content.insert(reference_start, start=True)
425            reference_end = ReferenceMarkEnd(name)
426            content.append(reference_end)
427            return reference_start
428
429        # With "content" => automatically insert a "start" and an "end"
430        # reference
431        if (
432            before is None
433            and after is None
434            and content is not None
435            and isinstance(position, int)
436        ):
437            # Start tag
438            reference_start = ReferenceMarkStart(name)
439            self._insert(
440                reference_start, before=content, position=position, main_text=True
441            )
442            # End tag
443            reference_end = ReferenceMarkEnd(name)
444            self._insert(
445                reference_end, after=content, position=position, main_text=True
446            )
447            return reference_start
448
449        # With "(int, int)" =>  automatically insert a "start" and an "end"
450        if (
451            before is None
452            and after is None
453            and content is None
454            and isinstance(position, tuple)
455        ):
456            # Start
457            reference_start = ReferenceMarkStart(name)
458            self._insert(reference_start, position=position[0], main_text=True)
459            # End
460            reference_end = ReferenceMarkEnd(name)
461            self._insert(reference_end, position=position[1], main_text=True)
462            return reference_start
463
464        # Without "content" nor "position"
465        if content is not None or not isinstance(position, int):
466            raise ValueError("bad arguments")
467
468        # Insert a positional reference mark
469        reference = ReferenceMark(name)
470        self._insert(
471            reference,
472            before=before,
473            after=after,
474            position=position,
475            main_text=True,
476        )
477        return reference

Insert a reference mark, at the position defined by the regex (before, after, content) or by positionnal argument (position). If content is provided, the annotation covers the full range content regex (instances of ReferenceMarkStart and ReferenceMarkEnd are created). Else, an instance of ReferenceMark is positionned either 'before' or 'after' provided regex.

If content is an ODF Element (ie: Paragraph, Span, ...), the full inner content is referenced (of the position just after if content is a single empty tag).

If content/before or after exists (regex) and return a group of matching positions, the position value is the index of matching place to use.

Name is mandatory and shall be unique in the document for the preference mark range.

Arguments:

name -- str

before -- str regular expression or None

after -- str regular expression or None,

content -- str regular expression or None, or Element

position -- int or tuple of int

Return: the created ReferenceMark or ReferenceMarkStart

def set_reference_mark_end( self, reference_mark: Element, before: str | None = None, after: str | None = None, position: int = 0) -> ReferenceMarkEnd:
479    def set_reference_mark_end(
480        self,
481        reference_mark: Element,
482        before: str | None = None,
483        after: str | None = None,
484        position: int = 0,
485    ) -> ReferenceMarkEnd:
486        """Insert/move a ReferenceMarkEnd for an existing reference mark. If
487        some end tag already exists, replace it. Reference tag is set at the
488        position defined by the regex (before or after).
489
490        If content/before or after (regex) returns a group of matching
491        positions, the position value is the index of matching place to use.
492
493        Arguments:
494
495            reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory)
496
497            before -- str regular expression or None
498
499            after -- str regular expression or None
500
501            position -- int
502        """
503        if not isinstance(reference_mark, (ReferenceMark, ReferenceMarkStart)):
504            raise TypeError("Not a ReferenceMark or ReferenceMarkStart")
505        name = reference_mark.name
506        if isinstance(reference_mark, ReferenceMark):
507            # change it to a range reference:
508            reference_mark.tag = ReferenceMarkStart._tag
509
510        existing_end_tag = self.get_reference_mark_end(name=name)
511        if existing_end_tag:
512            existing_end_tag.delete()
513
514        # create the end tag
515        end_tag = ReferenceMarkEnd(name)
516
517        # Insert
518        self._insert(
519            end_tag, before=before, after=after, position=position, main_text=True
520        )
521        return end_tag

Insert/move a ReferenceMarkEnd for an existing reference mark. If some end tag already exists, replace it. Reference tag is set at the position defined by the regex (before or after).

If content/before or after (regex) returns a group of matching positions, the position value is the index of matching place to use.

Arguments:

reference_mark -- ReferenceMark or ReferenceMarkStart (mandatory)

before -- str regular expression or None

after -- str regular expression or None

position -- int
def insert_variable(self, variable_element: Element, after: str | None) -> None:
523    def insert_variable(self, variable_element: Element, after: str | None) -> None:
524        self._insert(variable_element, after=after, main_text=True)
def set_span( self, match: str, tail: str, style: str, regex: str | None = None, offset: int | None = None, length: int = 0) -> Span:
526    @_by_regex_offset
527    def set_span(
528        self,
529        match: str,
530        tail: str,
531        style: str,
532        regex: str | None = None,
533        offset: int | None = None,
534        length: int = 0,
535    ) -> Span:
536        """
537        set_span(style, regex=None, offset=None, length=0)
538        Apply the given style to text content matching the regex OR the
539        positional arguments offset and length.
540
541        (match, tail: provided by regex decorator)
542
543        Arguments:
544
545            style -- str
546
547            regex -- str regular expression
548
549            offset -- int
550
551            length -- int
552        """
553        span = Span(match, style=style)
554        span.tail = tail
555        return span

set_span(style, regex=None, offset=None, length=0) Apply the given style to text content matching the regex OR the positional arguments offset and length.

(match, tail: provided by regex decorator)

Arguments:

style -- str

regex -- str regular expression

offset -- int

length -- int
def remove_spans(self, keep_heading: bool = True) -> Element | list:
557    def remove_spans(self, keep_heading: bool = True) -> Element | list:
558        """Send back a copy of the element, without span styles.
559        If keep_heading is True (default), the first level heading style is left
560        unchanged.
561        """
562        strip = (Span._tag,)
563        if keep_heading:
564            protect = ("text:h",)
565        else:
566            protect = None
567        return self.strip_tags(strip=strip, protect=protect)

Send back a copy of the element, without span styles. If keep_heading is True (default), the first level heading style is left unchanged.

def remove_span( self, spans: Element | list[Element]) -> Element | list:
569    def remove_span(self, spans: Element | list[Element]) -> Element | list:
570        """Send back a copy of the element, the spans (not a clone) removed.
571
572        Arguments:
573
574            spans -- Element or list of Element
575        """
576        return self.strip_elements(spans)

Send back a copy of the element, the spans (not a clone) removed.

Arguments:

spans -- Element or list of Element
def insert_reference( self, name: str, ref_format: str = '', before: str | None = None, after: str | Element | None = None, position: int = 0, display: str | None = None) -> None:
624    def insert_reference(
625        self,
626        name: str,
627        ref_format: str = "",
628        before: str | None = None,
629        after: str | Element | None = None,
630        position: int = 0,
631        display: str | None = None,
632    ) -> None:
633        """Create and insert a reference to a content marked by a reference
634        mark. The Reference element ("text:reference-ref") represents a
635        field that references a "text:reference-mark-start" or
636        "text:reference-mark" element. Its "text:reference-format" attribute
637        specifies what is displayed from the referenced element. Default is
638        'page'. Actual content is not automatically updated except for the 'text'
639        format.
640
641        name is mandatory and should represent an existing reference mark of the
642        document.
643
644        ref_format is the argument for format reference (default is 'page').
645
646        The reference is inserted the position defined by the regex (before /
647        after), or by positionnal argument (position). If 'display' is provided,
648        it will be used as the text value for the reference.
649
650        If after is an ODF Element, the reference is inserted as first child of
651        this element.
652
653        Arguments:
654
655            name -- str
656
657            ref_format -- one of : 'chapter', 'direction', 'page', 'text',
658                                    'caption', 'category-and-value', 'value',
659                                    'number', 'number-all-superior',
660                                    'number-no-superior'
661
662            before -- str regular expression or None
663
664            after -- str regular expression or odf element or None
665
666            position -- int
667
668            display -- str or None
669        """
670        reference = Reference(name, ref_format)
671        if display is None and ref_format == "text":
672            # get reference content
673            body = self.document_body
674            if not body:
675                body = self.root
676            mark = body.get_reference_mark(name=name)
677            if mark:
678                display = mark.referenced_text  # type: ignore
679        if not display:
680            display = " "
681        reference.text = display
682        if isinstance(after, Element):
683            after.insert(reference, FIRST_CHILD)
684        else:
685            self._insert(
686                reference, before=before, after=after, position=position, main_text=True
687            )

Create and insert a reference to a content marked by a reference mark. The Reference element ("text:reference-ref") represents a field that references a "text:reference-mark-start" or "text:reference-mark" element. Its "text:reference-format" attribute specifies what is displayed from the referenced element. Default is 'page'. Actual content is not automatically updated except for the 'text' format.

name is mandatory and should represent an existing reference mark of the document.

ref_format is the argument for format reference (default is 'page').

The reference is inserted the position defined by the regex (before / after), or by positionnal argument (position). If 'display' is provided, it will be used as the text value for the reference.

If after is an ODF Element, the reference is inserted as first child of this element.

Arguments:

name -- str

ref_format -- one of : 'chapter', 'direction', 'page', 'text',
                        'caption', 'category-and-value', 'value',
                        'number', 'number-all-superior',
                        'number-no-superior'

before -- str regular expression or None

after -- str regular expression or odf element or None

position -- int

display -- str or None
def set_bookmark( self, name: str, before: str | None = None, after: str | None = None, position: int | tuple = 0, role: str | None = None, content: str | None = None) -> Element | tuple[Element, Element]:
689    def set_bookmark(
690        self,
691        name: str,
692        before: str | None = None,
693        after: str | None = None,
694        position: int | tuple = 0,
695        role: str | None = None,
696        content: str | None = None,
697    ) -> Element | tuple[Element, Element]:
698        """Insert a bookmark before or after the characters in the text which
699        match the regex before/after. When the regex matches more of one part
700        of the text, position can be set to choose which part must be used.
701        If before and after are None, we use only position that is the number
702        of characters.
703
704        So, by default, this function inserts a bookmark before the first
705        character of the content. Role can be None, "start" or "end", we
706        insert respectively a position bookmark a bookmark-start or a
707        bookmark-end.
708
709        If content is not None these 2 calls are equivalent:
710
711          paragraph.set_bookmark("bookmark", content="xyz")
712
713        and:
714
715          paragraph.set_bookmark("bookmark", before="xyz", role="start")
716          paragraph.set_bookmark("bookmark", after="xyz", role="end")
717
718
719        If position is a 2-tuple, these 2 calls are equivalent:
720
721          paragraph.set_bookmark("bookmark", position=(10, 20))
722
723        and:
724
725          paragraph.set_bookmark("bookmark", position=10, role="start")
726          paragraph.set_bookmark("bookmark", position=20, role="end")
727
728
729        Arguments:
730
731            name -- str
732
733            before -- str regex
734
735            after -- str regex
736
737            position -- int or (int, int)
738
739            role -- None, "start" or "end"
740
741            content -- str regex
742        """
743        # With "content" => automatically insert a "start" and an "end"
744        # bookmark
745        if (
746            before is None
747            and after is None
748            and role is None
749            and content is not None
750            and isinstance(position, int)
751        ):
752            # Start
753            start = BookmarkStart(name)
754            self._insert(start, before=content, position=position, main_text=True)
755            # End
756            end = BookmarkEnd(name)
757            self._insert(end, after=content, position=position, main_text=True)
758            return start, end
759
760        # With "(int, int)" =>  automatically insert a "start" and an "end"
761        # bookmark
762        if (
763            before is None
764            and after is None
765            and role is None
766            and content is None
767            and isinstance(position, tuple)
768        ):
769            # Start
770            start = BookmarkStart(name)
771            self._insert(start, position=position[0], main_text=True)
772            # End
773            end = BookmarkEnd(name)
774            self._insert(end, position=position[1], main_text=True)
775            return start, end
776
777        # Without "content" nor "position"
778        if content is not None or not isinstance(position, int):
779            raise ValueError("bad arguments")
780
781        # Role
782        if role is None:
783            bookmark: Element = Bookmark(name)
784        elif role == "start":
785            bookmark = BookmarkStart(name)
786        elif role == "end":
787            bookmark = BookmarkEnd(name)
788        else:
789            raise ValueError("bad arguments")
790
791        # Insert
792        self._insert(
793            bookmark, before=before, after=after, position=position, main_text=True
794        )
795
796        return bookmark

Insert a bookmark before or after the characters in the text which match the regex before/after. When the regex matches more of one part of the text, position can be set to choose which part must be used. If before and after are None, we use only position that is the number of characters.

So, by default, this function inserts a bookmark before the first character of the content. Role can be None, "start" or "end", we insert respectively a position bookmark a bookmark-start or a bookmark-end.

If content is not None these 2 calls are equivalent:

paragraph.set_bookmark("bookmark", content="xyz")

and:

paragraph.set_bookmark("bookmark", before="xyz", role="start") paragraph.set_bookmark("bookmark", after="xyz", role="end")

If position is a 2-tuple, these 2 calls are equivalent:

paragraph.set_bookmark("bookmark", position=(10, 20))

and:

paragraph.set_bookmark("bookmark", position=10, role="start") paragraph.set_bookmark("bookmark", position=20, role="end")

Arguments:

name -- str

before -- str regex

after -- str regex

position -- int or (int, int)

role -- None, "start" or "end"

content -- str regex
Inherited Members
odfdo.paragraph_base.ParagraphBase
get_formatted_text
append_plain_text
style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class RectangleShape(odfdo.shapes.ShapeBase):
146class RectangleShape(ShapeBase):
147    """Create a rectangle shape.
148
149    Arguments:
150
151        style -- str
152
153        text_style -- str
154
155        draw_id -- str
156
157        layer -- str
158
159        position -- (str, str)
160
161        size -- (str, str)
162
163    """
164
165    _tag = "draw:rect"
166    _properties: tuple[PropDef, ...] = ()
167
168    def __init__(
169        self,
170        style: str | None = None,
171        text_style: str | None = None,
172        draw_id: str | None = None,
173        layer: str | None = None,
174        position: tuple | None = None,
175        size: tuple | None = None,
176        **kwargs: Any,
177    ) -> None:
178        kwargs.update(
179            {
180                "style": style,
181                "text_style": text_style,
182                "draw_id": draw_id,
183                "layer": layer,
184                "size": size,
185                "position": position,
186            }
187        )
188        super().__init__(**kwargs)

Create a rectangle shape.

Arguments:

style -- str

text_style -- str

draw_id -- str

layer -- str

position -- (str, str)

size -- (str, str)
RectangleShape( style: str | None = None, text_style: str | None = None, draw_id: str | None = None, layer: str | None = None, position: tuple | None = None, size: tuple | None = None, **kwargs: Any)
168    def __init__(
169        self,
170        style: str | None = None,
171        text_style: str | None = None,
172        draw_id: str | None = None,
173        layer: str | None = None,
174        position: tuple | None = None,
175        size: tuple | None = None,
176        **kwargs: Any,
177    ) -> None:
178        kwargs.update(
179            {
180                "style": style,
181                "text_style": text_style,
182                "draw_id": draw_id,
183                "layer": layer,
184                "size": size,
185                "position": position,
186            }
187        )
188        super().__init__(**kwargs)
Inherited Members
odfdo.shapes.ShapeBase
get_formatted_text
draw_id
layer
width
height
pos_x
pos_y
presentation_class
style
text_style
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
odfdo.frame.SizeMix
size
odfdo.frame.PosMix
position
class Reference(odfdo.Element):
 59class Reference(Element):
 60    """A reference to a content marked by a reference mark.
 61    The odf_reference element ("text:reference-ref") represents a field that
 62    references a "text:reference-mark-start" or "text:reference-mark" element.
 63    Its text:reference-format attribute specifies what is displayed from the
 64    referenced element. Default is 'page'
 65    Actual content is not updated except for the 'text' format by the
 66    update() method.
 67
 68
 69    Creation of references can be tricky, consider using this method:
 70        odfdo.paragraph.insert_reference()
 71
 72    Values for text:reference-format :
 73        The defined values for the text:reference-format attribute supported by
 74        all reference fields are:
 75          - 'chapter': displays the number of the chapter in which the
 76            referenced item appears.
 77          - 'direction': displays whether the referenced item is above or
 78            below the reference field.
 79          - 'page': displays the number of the page on which the referenced
 80            item appears.
 81          - 'text': displays the text of the referenced item.
 82        Additional defined values for the text:reference-format attribute
 83        supported by references to sequence fields are:
 84          - 'caption': displays the caption in which the sequence is used.
 85          - 'category-and-value': displays the name and value of the sequence.
 86          - 'value': displays the value of the sequence.
 87
 88        References to bookmarks and other references support additional values,
 89        which display the list label of the referenced item. If the referenced
 90        item is contained in a list or a numbered paragraph, the list label is
 91        the formatted number of the paragraph which contains the referenced
 92        item. If the referenced item is not contained in a list or numbered
 93        paragraph, the list label is empty, and the referenced field therefore
 94        displays nothing. If the referenced bookmark or reference contains more
 95        than one paragraph, the list label of the paragraph at which the
 96        bookmark or reference starts is taken.
 97
 98        Additional defined values for the text:reference-format attribute
 99        supported by all references to bookmark's or other reference fields
100        are:
101          - 'number': displays the list label of the referenced item. [...]
102          - 'number-all-superior': displays the list label of the referenced
103            item and adds the contents of all list labels of superior levels
104            in front of it. [...]
105          - 'number-no-superior': displays the contents of the list label of
106            the referenced item.
107    """
108
109    _tag = "text:reference-ref"
110    _properties = (PropDef("name", "text:ref-name"),)
111    format_allowed = (
112        "chapter",
113        "direction",
114        "page",
115        "text",
116        "caption",
117        "category-and-value",
118        "value",
119        "number",
120        "number-all-superior",
121        "number-no-superior",
122    )
123
124    def __init__(self, name: str = "", ref_format: str = "", **kwargs: Any) -> None:
125        """Create a reference to a content marked by a reference mark. An
126        actual reference mark with the provided name should exist.
127
128        Consider using: odfdo.paragraph.insert_reference()
129
130        The text:ref-name attribute identifies a "text:reference-mark" or
131        "text:referencemark-start" element by the value of that element's
132        text:name attribute.
133        If ref_format is 'text', the current text content of the reference_mark
134        is retrieved.
135
136        Arguments:
137
138            name -- str : name of the reference mark
139
140            ref_format -- str : format of the field. Default is 'page', allowed
141                            values are 'chapter', 'direction', 'page', 'text',
142                            'caption', 'category-and-value', 'value', 'number',
143                            'number-all-superior', 'number-no-superior'.
144        """
145        super().__init__(**kwargs)
146        if self._do_init:
147            self.name = name
148            self.ref_format = ref_format
149
150    @property
151    def ref_format(self) -> str | None:
152        reference = self.get_attribute("text:reference-format")
153        if isinstance(reference, str):
154            return reference
155        return None
156
157    @ref_format.setter
158    def ref_format(self, ref_format: str) -> None:
159        """Set the text:reference-format attribute.
160
161        Arguments:
162
163            ref_format -- str
164        """
165        if not ref_format or ref_format not in self.format_allowed:
166            ref_format = "page"
167        self.set_attribute("text:reference-format", ref_format)
168
169    def update(self) -> None:
170        """Update the content of the reference text field. Currently only
171        'text' format is implemented. Other values, for example the 'page' text
172        field, may need to be refreshed through a visual ODF parser.
173        """
174        ref_format = self.ref_format
175        if ref_format != "text":
176            # only 'text' is implemented
177            return None
178        body = self.document_body
179        if not body:
180            body = self.root
181        name = self.name
182        reference = body.get_reference_mark(name=name)
183        if not reference:
184            return None
185        # we know it is a ReferenceMarkStart:
186        self.text = reference.referenced_text()  # type: ignore

A reference to a content marked by a reference mark. The odf_reference element ("text:reference-ref") represents a field that references a "text:reference-mark-start" or "text:reference-mark" element. Its text:reference-format attribute specifies what is displayed from the referenced element. Default is 'page' Actual content is not updated except for the 'text' format by the update() method.

Creation of references can be tricky, consider using this method: odfdo.paragraph.insert_reference()

Values for text:reference-format : The defined values for the text:reference-format attribute supported by all reference fields are: - 'chapter': displays the number of the chapter in which the referenced item appears. - 'direction': displays whether the referenced item is above or below the reference field. - 'page': displays the number of the page on which the referenced item appears. - 'text': displays the text of the referenced item. Additional defined values for the text:reference-format attribute supported by references to sequence fields are: - 'caption': displays the caption in which the sequence is used. - 'category-and-value': displays the name and value of the sequence. - 'value': displays the value of the sequence.

References to bookmarks and other references support additional values,
which display the list label of the referenced item. If the referenced
item is contained in a list or a numbered paragraph, the list label is
the formatted number of the paragraph which contains the referenced
item. If the referenced item is not contained in a list or numbered
paragraph, the list label is empty, and the referenced field therefore
displays nothing. If the referenced bookmark or reference contains more
than one paragraph, the list label of the paragraph at which the
bookmark or reference starts is taken.

Additional defined values for the text:reference-format attribute
supported by all references to bookmark's or other reference fields
are:
  - 'number': displays the list label of the referenced item. [...]
  - 'number-all-superior': displays the list label of the referenced
    item and adds the contents of all list labels of superior levels
    in front of it. [...]
  - 'number-no-superior': displays the contents of the list label of
    the referenced item.
Reference(name: str = '', ref_format: str = '', **kwargs: Any)
124    def __init__(self, name: str = "", ref_format: str = "", **kwargs: Any) -> None:
125        """Create a reference to a content marked by a reference mark. An
126        actual reference mark with the provided name should exist.
127
128        Consider using: odfdo.paragraph.insert_reference()
129
130        The text:ref-name attribute identifies a "text:reference-mark" or
131        "text:referencemark-start" element by the value of that element's
132        text:name attribute.
133        If ref_format is 'text', the current text content of the reference_mark
134        is retrieved.
135
136        Arguments:
137
138            name -- str : name of the reference mark
139
140            ref_format -- str : format of the field. Default is 'page', allowed
141                            values are 'chapter', 'direction', 'page', 'text',
142                            'caption', 'category-and-value', 'value', 'number',
143                            'number-all-superior', 'number-no-superior'.
144        """
145        super().__init__(**kwargs)
146        if self._do_init:
147            self.name = name
148            self.ref_format = ref_format

Create a reference to a content marked by a reference mark. An actual reference mark with the provided name should exist.

Consider using: odfdo.paragraph.insert_reference()

The text:ref-name attribute identifies a "text:reference-mark" or "text:referencemark-start" element by the value of that element's text:name attribute. If ref_format is 'text', the current text content of the reference_mark is retrieved.

Arguments:

name -- str : name of the reference mark

ref_format -- str : format of the field. Default is 'page', allowed
                values are 'chapter', 'direction', 'page', 'text',
                'caption', 'category-and-value', 'value', 'number',
                'number-all-superior', 'number-no-superior'.
format_allowed = ('chapter', 'direction', 'page', 'text', 'caption', 'category-and-value', 'value', 'number', 'number-all-superior', 'number-no-superior')
ref_format: str | None
150    @property
151    def ref_format(self) -> str | None:
152        reference = self.get_attribute("text:reference-format")
153        if isinstance(reference, str):
154            return reference
155        return None

Set the text:reference-format attribute.

Arguments:

ref_format -- str
def update(self) -> None:
169    def update(self) -> None:
170        """Update the content of the reference text field. Currently only
171        'text' format is implemented. Other values, for example the 'page' text
172        field, may need to be refreshed through a visual ODF parser.
173        """
174        ref_format = self.ref_format
175        if ref_format != "text":
176            # only 'text' is implemented
177            return None
178        body = self.document_body
179        if not body:
180            body = self.root
181        name = self.name
182        reference = body.get_reference_mark(name=name)
183        if not reference:
184            return None
185        # we know it is a ReferenceMarkStart:
186        self.text = reference.referenced_text()  # type: ignore

Update the content of the reference text field. Currently only 'text' format is implemented. Other values, for example the 'page' text field, may need to be refreshed through a visual ODF parser.

name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ReferenceMark(odfdo.Element):
192class ReferenceMark(Element):
193    """A point reference.
194    A point reference marks a position in text and is represented by a single
195    "text:reference-mark" element.
196    """
197
198    _tag = "text:reference-mark"
199    _properties = (PropDef("name", "text:name"),)
200
201    def __init__(self, name: str = "", **kwargs: Any) -> None:
202        """A point reference. A point reference marks a position in text and is
203        represented by a single "text:reference-mark" element.
204        Consider using the wrapper: odfdo.paragraph.set_reference_mark()
205
206        Arguments:
207
208            name -- str
209        """
210        super().__init__(**kwargs)
211        if self._do_init:
212            self.name = name

A point reference. A point reference marks a position in text and is represented by a single "text:reference-mark" element.

ReferenceMark(name: str = '', **kwargs: Any)
201    def __init__(self, name: str = "", **kwargs: Any) -> None:
202        """A point reference. A point reference marks a position in text and is
203        represented by a single "text:reference-mark" element.
204        Consider using the wrapper: odfdo.paragraph.set_reference_mark()
205
206        Arguments:
207
208            name -- str
209        """
210        super().__init__(**kwargs)
211        if self._do_init:
212            self.name = name

A point reference. A point reference marks a position in text and is represented by a single "text:reference-mark" element. Consider using the wrapper: odfdo.paragraph.set_reference_mark()

Arguments:

name -- str
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ReferenceMarkEnd(odfdo.Element):
218class ReferenceMarkEnd(Element):
219    """The "text:reference-mark-end" element represents the end of a range
220    reference.
221    """
222
223    _tag = "text:reference-mark-end"
224    _properties = (PropDef("name", "text:name"),)
225
226    def __init__(self, name: str = "", **kwargs: Any) -> None:
227        """The "text:reference-mark-end" element represent the end of a range
228        reference.
229        Consider using the wrappers: odfdo.paragraph.set_reference_mark() and
230        odfdo.paragraph.set_reference_mark_end()
231
232        Arguments:
233
234            name -- str
235        """
236        super().__init__(**kwargs)
237        if self._do_init:
238            self.name = name
239
240    def referenced_text(self) -> str:
241        """Return the text between reference-mark-start and reference-mark-end."""
242        name = self.name
243        request = (
244            f"//text()"
245            f"[preceding::text:reference-mark-start[@text:name='{name}'] "
246            f"and following::text:reference-mark-end[@text:name='{name}']]"
247        )
248        result = " ".join(str(x) for x in self.xpath(request))
249        return result

The "text:reference-mark-end" element represents the end of a range reference.

ReferenceMarkEnd(name: str = '', **kwargs: Any)
226    def __init__(self, name: str = "", **kwargs: Any) -> None:
227        """The "text:reference-mark-end" element represent the end of a range
228        reference.
229        Consider using the wrappers: odfdo.paragraph.set_reference_mark() and
230        odfdo.paragraph.set_reference_mark_end()
231
232        Arguments:
233
234            name -- str
235        """
236        super().__init__(**kwargs)
237        if self._do_init:
238            self.name = name

The "text:reference-mark-end" element represent the end of a range reference. Consider using the wrappers: odfdo.paragraph.set_reference_mark() and odfdo.paragraph.set_reference_mark_end()

Arguments:

name -- str
def referenced_text(self) -> str:
240    def referenced_text(self) -> str:
241        """Return the text between reference-mark-start and reference-mark-end."""
242        name = self.name
243        request = (
244            f"//text()"
245            f"[preceding::text:reference-mark-start[@text:name='{name}'] "
246            f"and following::text:reference-mark-end[@text:name='{name}']]"
247        )
248        result = " ".join(str(x) for x in self.xpath(request))
249        return result

Return the text between reference-mark-start and reference-mark-end.

name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class ReferenceMarkStart(odfdo.Element):
255class ReferenceMarkStart(Element):
256    """The "text:reference-mark-start" element represents the start of a
257    range reference.
258    """
259
260    _tag = "text:reference-mark-start"
261    _properties = (PropDef("name", "text:name"),)
262
263    def __init__(self, name: str = "", **kwargs: Any) -> None:
264        """The "text:reference-mark-start" element represent the start of a range
265        reference.
266        Consider using the wrapper: odfdo.paragraph.set_reference_mark()
267
268        Arguments:
269
270            name -- str
271        """
272        super().__init__(**kwargs)
273        if self._do_init:
274            self.name = name
275
276    def referenced_text(self) -> str:
277        """Return the text between reference-mark-start and reference-mark-end."""
278        name = self.name
279        request = (
280            f"//text()"
281            f"[preceding::text:reference-mark-start[@text:name='{name}'] "
282            f"and following::text:reference-mark-end[@text:name='{name}']]"
283        )
284        result = " ".join(str(x) for x in self.xpath(request))
285        return result
286
287    def get_referenced(
288        self,
289        no_header: bool = False,
290        clean: bool = True,
291        as_xml: bool = False,
292        as_list: bool = False,
293    ) -> Element | list | str | None:
294        """Return the document content between the start and end tags of the
295        reference. The content returned by this method can spread over several
296        headers and paragraphs.
297        By default, the content is returned as an "office:text" odf element.
298
299
300        Arguments:
301
302            no_header -- boolean (default to False), translate existing headers
303                         tags "text:h" into paragraphs "text:p".
304
305            clean -- boolean (default to True), suppress unwanted tags. Striped
306                     tags are : 'text:change', 'text:change-start',
307                     'text:change-end', 'text:reference-mark',
308                     'text:reference-mark-start', 'text:reference-mark-end'.
309
310            as_xml -- boolean (default to False), format the returned content as
311                      a XML string (serialization).
312
313            as_list -- boolean (default to False), do not embed the returned
314                       content in a "office:text'" element, instead simply
315                       return a raw list of odf elements.
316        """
317        name = self.name
318        parent = self.parent
319        if parent is None:
320            raise ValueError("Reference need some upper document part")
321        body = self.document_body
322        if not body:
323            body = parent
324        end = body.get_reference_mark_end(name=name)
325        if end is None:
326            raise ValueError("No reference-end found")
327        start = self
328        return _get_referenced(body, start, end, no_header, clean, as_xml, as_list)
329
330    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
331        """Delete the given element from the XML tree. If no element is given,
332        "self" is deleted. The XML library may allow to continue to use an
333        element now "orphan" as long as you have a reference to it.
334
335        For odf_reference_mark_start : delete the reference-end tag if exists.
336
337        Arguments:
338
339            child -- Element
340
341            keep_tail -- boolean (default to True), True for most usages.
342        """
343        if child is not None:  # act like normal delete
344            return super().delete(child, keep_tail)
345        name = self.name
346        parent = self.parent
347        if parent is None:
348            raise ValueError("Can't delete the root element")
349        body = self.document_body
350        if not body:
351            body = parent
352        end = body.get_reference_mark_end(name=name)
353        if end:
354            end.delete()
355        # act like normal delete
356        return super().delete()

The "text:reference-mark-start" element represents the start of a range reference.

ReferenceMarkStart(name: str = '', **kwargs: Any)
263    def __init__(self, name: str = "", **kwargs: Any) -> None:
264        """The "text:reference-mark-start" element represent the start of a range
265        reference.
266        Consider using the wrapper: odfdo.paragraph.set_reference_mark()
267
268        Arguments:
269
270            name -- str
271        """
272        super().__init__(**kwargs)
273        if self._do_init:
274            self.name = name

The "text:reference-mark-start" element represent the start of a range reference. Consider using the wrapper: odfdo.paragraph.set_reference_mark()

Arguments:

name -- str
def referenced_text(self) -> str:
276    def referenced_text(self) -> str:
277        """Return the text between reference-mark-start and reference-mark-end."""
278        name = self.name
279        request = (
280            f"//text()"
281            f"[preceding::text:reference-mark-start[@text:name='{name}'] "
282            f"and following::text:reference-mark-end[@text:name='{name}']]"
283        )
284        result = " ".join(str(x) for x in self.xpath(request))
285        return result

Return the text between reference-mark-start and reference-mark-end.

def get_referenced( self, no_header: bool = False, clean: bool = True, as_xml: bool = False, as_list: bool = False) -> Element | list | str | None:
287    def get_referenced(
288        self,
289        no_header: bool = False,
290        clean: bool = True,
291        as_xml: bool = False,
292        as_list: bool = False,
293    ) -> Element | list | str | None:
294        """Return the document content between the start and end tags of the
295        reference. The content returned by this method can spread over several
296        headers and paragraphs.
297        By default, the content is returned as an "office:text" odf element.
298
299
300        Arguments:
301
302            no_header -- boolean (default to False), translate existing headers
303                         tags "text:h" into paragraphs "text:p".
304
305            clean -- boolean (default to True), suppress unwanted tags. Striped
306                     tags are : 'text:change', 'text:change-start',
307                     'text:change-end', 'text:reference-mark',
308                     'text:reference-mark-start', 'text:reference-mark-end'.
309
310            as_xml -- boolean (default to False), format the returned content as
311                      a XML string (serialization).
312
313            as_list -- boolean (default to False), do not embed the returned
314                       content in a "office:text'" element, instead simply
315                       return a raw list of odf elements.
316        """
317        name = self.name
318        parent = self.parent
319        if parent is None:
320            raise ValueError("Reference need some upper document part")
321        body = self.document_body
322        if not body:
323            body = parent
324        end = body.get_reference_mark_end(name=name)
325        if end is None:
326            raise ValueError("No reference-end found")
327        start = self
328        return _get_referenced(body, start, end, no_header, clean, as_xml, as_list)

Return the document content between the start and end tags of the reference. The content returned by this method can spread over several headers and paragraphs. By default, the content is returned as an "office:text" odf element.

Arguments:

no_header -- boolean (default to False), translate existing headers
             tags "text:h" into paragraphs "text:p".

clean -- boolean (default to True), suppress unwanted tags. Striped
         tags are : 'text:change', 'text:change-start',
         'text:change-end', 'text:reference-mark',
         'text:reference-mark-start', 'text:reference-mark-end'.

as_xml -- boolean (default to False), format the returned content as
          a XML string (serialization).

as_list -- boolean (default to False), do not embed the returned
           content in a "office:text'" element, instead simply
           return a raw list of odf elements.
def delete( self, child: Element | None = None, keep_tail: bool = True) -> None:
330    def delete(self, child: Element | None = None, keep_tail: bool = True) -> None:
331        """Delete the given element from the XML tree. If no element is given,
332        "self" is deleted. The XML library may allow to continue to use an
333        element now "orphan" as long as you have a reference to it.
334
335        For odf_reference_mark_start : delete the reference-end tag if exists.
336
337        Arguments:
338
339            child -- Element
340
341            keep_tail -- boolean (default to True), True for most usages.
342        """
343        if child is not None:  # act like normal delete
344            return super().delete(child, keep_tail)
345        name = self.name
346        parent = self.parent
347        if parent is None:
348            raise ValueError("Can't delete the root element")
349        body = self.document_body
350        if not body:
351            body = parent
352        end = body.get_reference_mark_end(name=name)
353        if end:
354            end.delete()
355        # act like normal delete
356        return super().delete()

Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.

For odf_reference_mark_start : delete the reference-end tag if exists.

Arguments:

child -- Element

keep_tail -- boolean (default to True), True for most usages.
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Row(odfdo.Element):
 52class Row(Element):
 53    """ODF table row "table:table-row" """
 54
 55    _tag = "table:table-row"
 56    _caching = True
 57    _append = Element.append
 58
 59    def __init__(
 60        self,
 61        width: int | None = None,
 62        repeated: int | None = None,
 63        style: str | None = None,
 64        **kwargs: Any,
 65    ) -> None:
 66        """create a Row, optionally filled with "width" number of cells.
 67
 68        Rows contain cells, their number determine the number of columns.
 69
 70        You don't generally have to create rows by hand, use the Table API.
 71
 72        Arguments:
 73
 74            width -- int
 75
 76            repeated -- int
 77
 78            style -- str
 79        """
 80        super().__init__(**kwargs)
 81        self.y = None
 82        if not hasattr(self, "_indexes"):
 83            self._indexes = {}
 84            self._indexes["_rmap"] = {}
 85        if not hasattr(self, "_rmap"):
 86            self._compute_row_cache()
 87            if not hasattr(self, "_tmap"):
 88                self._tmap = []
 89                self._cmap = []
 90        if self._do_init:
 91            if width is not None:
 92                for _i in range(width):
 93                    self.append(Cell())  # type:ignore
 94            if repeated:
 95                self.repeated = repeated
 96            if style is not None:
 97                self.style = style
 98            self._compute_row_cache()
 99
100    def __repr__(self) -> str:
101        return f"<{self.__class__.__name__} y={self.y}>"
102
103    def _get_cells(self) -> list[Element]:
104        return self.get_elements(_xpath_cell)
105
106    def _translate_row_coordinates(
107        self,
108        coord: tuple | list | str,
109    ) -> tuple[int | None, int | None]:
110        xyzt = convert_coordinates(coord)
111        if len(xyzt) == 2:
112            x, z = xyzt
113        else:
114            x, _, z, __ = xyzt
115        if x and x < 0:
116            x = increment(x, self.width)
117        if z and z < 0:
118            z = increment(z, self.width)
119        return (x, z)
120
121    def _compute_row_cache(self) -> None:
122        idx_repeated_seq = self.elements_repeated_sequence(
123            _xpath_cell, "table:number-columns-repeated"
124        )
125        self._rmap = make_cache_map(idx_repeated_seq)
126
127    # Public API
128
129    @property
130    def clone(self) -> Row:
131        clone = Element.clone.fget(self)  # type: ignore
132        clone.y = self.y
133        if hasattr(self, "_tmap"):
134            if hasattr(self, "_rmap"):
135                clone._rmap = self._rmap[:]
136            clone._tmap = self._tmap[:]
137            clone._cmap = self._cmap[:]
138        return clone
139
140    def _set_repeated(self, repeated: int | None) -> None:
141        """Method Internal only. Set the numnber of times the row is
142        repeated, or None to delete it. Without changing cache.
143
144        Arguments:
145
146            repeated -- int
147        """
148        if repeated is None or repeated < 2:
149            with contextlib.suppress(KeyError):
150                self.del_attribute("table:number-rows-repeated")
151            return
152        self.set_attribute("table:number-rows-repeated", str(repeated))
153
154    @property
155    def repeated(self) -> int | None:
156        """Get / set the number of times the row is repeated.
157
158        Always None when using the table API.
159
160        Return: int or None
161        """
162        repeated = self.get_attribute("table:number-rows-repeated")
163        if repeated is None:
164            return None
165        return int(repeated)
166
167    @repeated.setter
168    def repeated(self, repeated: int | None) -> None:
169        self._set_repeated(repeated)
170        # update cache
171        current: Element = self
172        while True:
173            # look for Table, parent may be group of rows
174            upper = current.parent
175            if not upper:
176                # lonely row
177                return
178            # parent may be group of rows, not table
179            if isinstance(upper, Element) and upper._tag == "table:table":
180                break
181            current = upper
182        # fixme : need to optimize this
183        if isinstance(upper, Element) and upper._tag == "table:table":
184            upper._compute_table_cache()
185            if hasattr(self, "_tmap"):
186                del self._tmap[:]
187                self._tmap.extend(upper._tmap)
188            else:
189                self._tmap = upper._tmap
190
191    @property
192    def style(self) -> str | None:
193        """Get /set the style of the row itself.
194
195        Return: str
196        """
197        return self.get_attribute("table:style-name")  # type: ignore
198
199    @style.setter
200    def style(self, style: str | Element) -> None:
201        self.set_style_attribute("table:style-name", style)
202
203    @property
204    def width(self) -> int:
205        """Get the number of expected cells in the row, i.e. addition
206        repetitions.
207
208        Return: int
209        """
210        try:
211            value = self._rmap[-1] + 1
212        except Exception:
213            value = 0
214        return value
215
216    def _translate_x_from_any(self, x: str | int) -> int:
217        return translate_from_any(x, self.width, 0)
218
219    def traverse(  # noqa: C901
220        self,
221        start: int | None = None,
222        end: int | None = None,
223    ) -> Iterator[Cell]:
224        """Yield as many cell elements as expected cells in the row, i.e.
225        expand repetitions by returning the same cell as many times as
226        necessary.
227
228            Arguments:
229
230                start -- int
231
232                end -- int
233
234        Copies are returned, use set_cell() to push them back.
235        """
236        idx = -1
237        before = -1
238        x = 0
239        cell: Cell
240        if start is None and end is None:
241            for juska in self._rmap:
242                idx += 1
243                if idx in self._indexes["_rmap"]:
244                    cell = self._indexes["_rmap"][idx]
245                else:
246                    cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
247                    if not isinstance(cell, Cell):
248                        raise TypeError(f"Not a cell: {cell!r}")
249                    self._indexes["_rmap"][idx] = cell
250                repeated = juska - before
251                before = juska
252                for _i in range(repeated or 1):
253                    # Return a copy without the now obsolete repetition
254                    if cell is None:
255                        cell = Cell()
256                    else:
257                        cell = cell.clone
258                        if repeated > 1:
259                            cell.repeated = None
260                    cell.y = self.y
261                    cell.x = x
262                    x += 1
263                    yield cell
264        else:
265            if start is None:
266                start = 0
267            start = max(0, start)
268            if end is None:
269                try:
270                    end = self._rmap[-1]
271                except Exception:
272                    end = -1
273            start_map = find_odf_idx(self._rmap, start)
274            if start_map is None:
275                return
276            if start_map > 0:
277                before = self._rmap[start_map - 1]
278            idx = start_map - 1
279            before = start - 1
280            x = start
281            for juska in self._rmap[start_map:]:
282                idx += 1
283                if idx in self._indexes["_rmap"]:
284                    cell = self._indexes["_rmap"][idx]
285                else:
286                    cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
287                    if not isinstance(cell, Cell):
288                        raise TypeError(f"Not a cell: {cell!r}")
289                    self._indexes["_rmap"][idx] = cell
290                repeated = juska - before
291                before = juska
292                for _i in range(repeated or 1):
293                    if x <= end:
294                        if cell is None:
295                            cell = Cell()
296                        else:
297                            cell = cell.clone
298                            if repeated > 1 or (x == start and start > 0):
299                                cell.repeated = None
300                        cell.y = self.y
301                        cell.x = x
302                        x += 1
303                        yield cell
304
305    def get_cells(
306        self,
307        coord: str | tuple | None = None,
308        style: str | None = None,
309        content: str | None = None,
310        cell_type: str | None = None,
311    ) -> list[Cell]:
312        """Get the list of cells matching the criteria.
313
314        Filter by cell_type, with cell_type 'all' will retrieve cells of any
315        type, aka non empty cells.
316
317        Filter by coordinates will retrieve the amount of cells defined by
318        'coord', minus the other filters.
319
320        Arguments:
321
322            coord -- str or tuple of int : coordinates
323
324            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
325                         'currency', 'percentage' or 'all'
326
327            content -- str regex
328
329            style -- str
330
331        Return: list of Cell
332        """
333        # fixme : not clones ?
334        if coord:
335            x, z = self._translate_row_coordinates(coord)
336        else:
337            x = None
338            z = None
339        if cell_type:
340            cell_type = cell_type.lower().strip()
341        cells: list[Cell] = []
342        for cell in self.traverse(start=x, end=z):
343            # Filter the cells by cell_type
344            if cell_type:
345                ctype = cell.type
346                if not ctype or not (ctype == cell_type or cell_type == "all"):
347                    continue
348            # Filter the cells with the regex
349            if content and not cell.match(content):
350                continue
351            # Filter the cells with the style
352            if style and style != cell.style:
353                continue
354            cells.append(cell)
355        return cells
356
357    def _get_cell2(self, x: int, clone: bool = True) -> Cell | None:
358        if x >= self.width:
359            return Cell()
360        if clone:
361            return self._get_cell2_base(x).clone  # type: ignore
362        else:
363            return self._get_cell2_base(x)
364
365    def _get_cell2_base(self, x: int) -> Cell | None:
366        idx = find_odf_idx(self._rmap, x)
367        cell: Cell
368        if idx is not None:
369            if idx in self._indexes["_rmap"]:
370                cell = self._indexes["_rmap"][idx]
371            else:
372                cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
373                self._indexes["_rmap"][idx] = cell
374            return cell
375        return None
376
377    def get_cell(self, x: int, clone: bool = True) -> Cell | None:
378        """Get the cell at position "x" starting from 0. Alphabetical
379        positions like "D" are accepted.
380
381        A  copy is returned, use set_cell() to push it back.
382
383        Arguments:
384
385            x -- int or str
386
387        Return: Cell | None
388        """
389        x = self._translate_x_from_any(x)
390        cell = self._get_cell2(x, clone=clone)
391        if not cell:
392            return None
393        cell.y = self.y
394        cell.x = x
395        return cell
396
397    def get_value(
398        self,
399        x: int | str,
400        get_type: bool = False,
401    ) -> Any | tuple[Any, str]:
402        """Shortcut to get the value of the cell at position "x".
403        If get_type is True, returns the tuples (value, ODF type).
404
405        If the cell is empty, returns None or (None, None)
406
407        See get_cell() and Cell.get_value().
408        """
409        if get_type:
410            x = self._translate_x_from_any(x)
411            cell = self._get_cell2_base(x)
412            if cell is None:
413                return (None, None)
414            return cell.get_value(get_type=get_type)
415        x = self._translate_x_from_any(x)
416        cell = self._get_cell2_base(x)
417        if cell is None:
418            return None
419        return cell.get_value()
420
421    def set_cell(
422        self,
423        x: int | str,
424        cell: Cell | None = None,
425        clone: bool = True,
426    ) -> Cell:
427        """Push the cell back in the row at position "x" starting from 0.
428        Alphabetical positions like "D" are accepted.
429
430        Arguments:
431
432            x -- int or str
433
434        returns the cell with x and y updated
435        """
436        cell_back: Cell
437        if cell is None:
438            cell = Cell()
439            repeated = 1
440            clone = False
441        else:
442            repeated = cell.repeated or 1
443        x = self._translate_x_from_any(x)
444        # Outside the defined row
445        diff = x - self.width
446        if diff == 0:
447            cell_back = self.append_cell(cell, _repeated=repeated, clone=clone)
448        elif diff > 0:
449            self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False)
450            cell_back = self.append_cell(cell, _repeated=repeated, clone=clone)
451        else:
452            # Inside the defined row
453            set_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap", clone=clone)
454            cell.x = x
455            cell.y = self.y
456            cell_back = cell
457        return cell_back
458
459    def set_value(
460        self,
461        x: int | str,
462        value: Any,
463        style: str | None = None,
464        cell_type: str | None = None,
465        currency: str | None = None,
466    ) -> None:
467        """Shortcut to set the value of the cell at position "x".
468
469        Arguments:
470
471            x -- int or str
472
473            value -- Python type
474
475            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
476                     'string' or 'time'
477
478            currency -- three-letter str
479
480            style -- str
481
482        See get_cell() and Cell.get_value().
483        """
484        self.set_cell(
485            x,
486            Cell(value, style=style, cell_type=cell_type, currency=currency),
487            clone=False,
488        )
489
490    def insert_cell(
491        self,
492        x: int | str,
493        cell: Cell | None = None,
494        clone: bool = True,
495    ) -> Cell:
496        """Insert the given cell at position "x" starting from 0. If no cell
497        is given, an empty one is created.
498
499        Alphabetical positions like "D" are accepted.
500
501        Do not use when working on a table, use Table.insert_cell().
502
503        Arguments:
504
505            x -- int or str
506
507            cell -- Cell
508
509        returns the cell with x and y updated
510        """
511        cell_back: Cell
512        if cell is None:
513            cell = Cell()
514        x = self._translate_x_from_any(x)
515        # Outside the defined row
516        diff = x - self.width
517        if diff < 0:
518            insert_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap")
519            cell.x = x
520            cell.y = self.y
521            cell_back = cell
522        elif diff == 0:
523            cell_back = self.append_cell(cell, clone=clone)
524        else:
525            self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False)
526            cell_back = self.append_cell(cell, clone=clone)
527        return cell_back
528
529    def extend_cells(self, cells: Iterable[Cell] | None = None) -> None:
530        if cells is None:
531            cells = []
532        self.extend(cells)
533        self._compute_row_cache()
534
535    def append_cell(
536        self,
537        cell: Cell | None = None,
538        clone: bool = True,
539        _repeated: int | None = None,
540    ) -> Cell:
541        """Append the given cell at the end of the row. Repeated cells are
542        accepted. If no cell is given, an empty one is created.
543
544        Do not use when working on a table, use Table.append_cell().
545
546        Arguments:
547
548            cell -- Cell
549
550            _repeated -- (optional), repeated value of the row
551
552        returns the cell with x and y updated
553        """
554        if cell is None:
555            cell = Cell()
556            clone = False
557        if clone:
558            cell = cell.clone
559        self._append(cell)
560        if _repeated is None:
561            _repeated = cell.repeated or 1
562        self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated)
563        cell.x = self.width - 1
564        cell.y = self.y
565        return cell
566
567    # fix for unit test and typos
568    append = append_cell  # type: ignore
569
570    def delete_cell(self, x: int | str) -> None:
571        """Delete the cell at the given position "x" starting from 0.
572        Alphabetical positions like "D" are accepted.
573
574        Cells on the right will be shifted to the left. In a table, other
575        rows remain unaffected.
576
577        Arguments:
578
579            x -- int or str
580        """
581        x = self._translate_x_from_any(x)
582        if x >= self.width:
583            return
584        delete_item_in_vault(x, self, _xpath_cell_idx, "_rmap")
585
586    def get_values(
587        self,
588        coord: str | tuple | None = None,
589        cell_type: str | None = None,
590        complete: bool = False,
591        get_type: bool = False,
592    ) -> list[Any | tuple[Any, Any]]:
593        """Shortcut to get the cell values in this row.
594
595        Filter by cell_type, with cell_type 'all' will retrieve cells of any
596        type, aka non empty cells.
597        If cell_type is used and complete is True, missing values are
598        replaced by None.
599        If cell_type is None, complete is always True : with no cell type
600        queried, get_values() returns None for each empty cell, the length
601        of the list is equal to the length of the row (depending on
602        coordinates use).
603
604        If get_type is True, returns a tuple (value, ODF type of value), or
605        (None, None) for empty cells if complete is True.
606
607        Filter by coordinates will retrieve the amount of cells defined by
608        coordinates with None for empty cells, except when using cell_type.
609
610
611        Arguments:
612
613            coord -- str or tuple of int : coordinates in row
614
615            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
616                         'currency', 'percentage' or 'all'
617
618            complete -- boolean
619
620            get_type -- boolean
621
622        Return: list of Python types, or list of tuples.
623        """
624        if coord:
625            x, z = self._translate_row_coordinates(coord)
626        else:
627            x = None
628            z = None
629        if cell_type:
630            cell_type = cell_type.lower().strip()
631            values: list[Any | tuple[Any, Any]] = []
632            for cell in self.traverse(start=x, end=z):
633                # Filter the cells by cell_type
634                ctype = cell.type
635                if not ctype or not (ctype == cell_type or cell_type == "all"):
636                    if complete:
637                        if get_type:
638                            values.append((None, None))
639                        else:
640                            values.append(None)
641                    continue
642                values.append(cell.get_value(get_type=get_type))
643            return values
644        else:
645            return [
646                cell.get_value(get_type=get_type)
647                for cell in self.traverse(start=x, end=z)
648            ]
649
650    def set_cells(
651        self,
652        cells: list[Cell] | tuple[Cell] | None = None,
653        start: int | str = 0,
654        clone: bool = True,
655    ) -> None:
656        """Set the cells in the row, from the 'start' column.
657        This method does not clear the row, use row.clear() before to start
658        with an empty row.
659
660        Arguments:
661
662            cells -- list of cells
663
664            start -- int or str
665        """
666        if cells is None:
667            cells = []
668        if start is None:
669            start = 0
670        else:
671            start = self._translate_x_from_any(start)
672        if start == 0 and clone is False and (len(cells) >= self.width):
673            self.clear()
674            self.extend_cells(cells)
675        else:
676            x = start
677            for cell in cells:
678                self.set_cell(x, cell, clone=clone)
679                if cell:
680                    x += cell.repeated or 1
681                else:
682                    x += 1
683
684    def set_values(
685        self,
686        values: list[Any],
687        start: int | str = 0,
688        style: str | None = None,
689        cell_type: str | None = None,
690        currency: str | None = None,
691    ) -> None:
692        """Shortcut to set the value of cells in the row, from the 'start'
693        column vith values.
694        This method does not clear the row, use row.clear() before to start
695        with an empty row.
696
697        Arguments:
698
699            values -- list of Python types
700
701            start -- int or str
702
703            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
704                         'currency' or 'percentage'
705
706            currency -- three-letter str
707
708            style -- cell style
709        """
710        # fixme : if values n, n+ are same, use repeat
711        if start is None:
712            start = 0
713        else:
714            start = self._translate_x_from_any(start)
715        if start == 0 and (len(values) >= self.width):
716            self.clear()
717            cells = [
718                Cell(value, style=style, cell_type=cell_type, currency=currency)
719                for value in values
720            ]
721            self.extend_cells(cells)
722        else:
723            x = start
724            for value in values:
725                self.set_cell(
726                    x,
727                    Cell(value, style=style, cell_type=cell_type, currency=currency),
728                    clone=False,
729                )
730                x += 1
731
732    def rstrip(self, aggressive: bool = False) -> None:
733        """Remove *in-place* empty cells at the right of the row. An empty
734        cell has no value but can have style. If "aggressive" is True, style
735        is ignored.
736
737        Arguments:
738
739            aggressive -- bool
740        """
741        for cell in reversed(self._get_cells()):
742            if not cell.is_empty(aggressive=aggressive):  # type: ignore
743                break
744            self.delete(cell)
745        self._compute_row_cache()
746        self._indexes["_rmap"] = {}
747
748    def _current_length(self) -> int:
749        """Return the current estimated length of the row.
750
751        Return: int
752        """
753        idx_repeated_seq = self.elements_repeated_sequence(
754            _xpath_cell, "table:number-columns-repeated"
755        )
756        repeated = [item[1] for item in idx_repeated_seq]
757        if repeated:
758            return sum(repeated)
759        return 1
760
761    def minimized_width(self) -> int:
762        """Return the length of the row if the last repeated sequence is
763        reduced to one.
764
765        Return: int
766        """
767        idx_repeated_seq = self.elements_repeated_sequence(
768            _xpath_cell, "table:number-columns-repeated"
769        )
770        repeated = [item[1] for item in idx_repeated_seq]
771        if repeated:
772            cell = self.last_cell()
773            if cell is not None and cell.is_empty(aggressive=True):
774                repeated[-1] = 1
775            min_width = sum(repeated)
776        else:
777            min_width = 1
778        self._compute_row_cache()
779        self._indexes["_rmap"] = {}
780        return min_width
781
782    def last_cell(self) -> Cell | None:
783        """Return the las cell of the row.
784
785        Return Cell | None
786        """
787        try:
788            return self._get_cells()[-1]  # type: ignore
789        except IndexError:
790            return None
791
792    def force_width(self, width: int) -> None:
793        """Change the repeated property of the last cell of the row
794        to comply with the required max width.
795
796        Arguments:
797
798            width -- int
799        """
800        cell = self.last_cell()
801        if cell is None or not cell.is_empty(aggressive=True):
802            return
803        repeated = cell.repeated
804        if repeated is None:
805            return
806        # empty repeated cell
807        delta = self._current_length() - width
808        if delta > 0:
809            cell._set_repeated(repeated - delta)
810            self._compute_row_cache()
811
812    def is_empty(self, aggressive: bool = False) -> bool:
813        """Return whether every cell in the row has no value or the value
814        evaluates to False (empty string), and no style.
815
816        If aggressive is True, empty cells with style are considered empty.
817
818        Arguments:
819
820            aggressive -- bool
821
822        Return: bool
823        """
824        return all(cell.is_empty(aggressive=aggressive) for cell in self._get_cells())  # type: ignore

ODF table row "table:table-row"

Row( width: int | None = None, repeated: int | None = None, style: str | None = None, **kwargs: Any)
59    def __init__(
60        self,
61        width: int | None = None,
62        repeated: int | None = None,
63        style: str | None = None,
64        **kwargs: Any,
65    ) -> None:
66        """create a Row, optionally filled with "width" number of cells.
67
68        Rows contain cells, their number determine the number of columns.
69
70        You don't generally have to create rows by hand, use the Table API.
71
72        Arguments:
73
74            width -- int
75
76            repeated -- int
77
78            style -- str
79        """
80        super().__init__(**kwargs)
81        self.y = None
82        if not hasattr(self, "_indexes"):
83            self._indexes = {}
84            self._indexes["_rmap"] = {}
85        if not hasattr(self, "_rmap"):
86            self._compute_row_cache()
87            if not hasattr(self, "_tmap"):
88                self._tmap = []
89                self._cmap = []
90        if self._do_init:
91            if width is not None:
92                for _i in range(width):
93                    self.append(Cell())  # type:ignore
94            if repeated:
95                self.repeated = repeated
96            if style is not None:
97                self.style = style
98            self._compute_row_cache()

create a Row, optionally filled with "width" number of cells.

Rows contain cells, their number determine the number of columns.

You don't generally have to create rows by hand, use the Table API.

Arguments:

width -- int

repeated -- int

style -- str
y
clone: Row
129    @property
130    def clone(self) -> Row:
131        clone = Element.clone.fget(self)  # type: ignore
132        clone.y = self.y
133        if hasattr(self, "_tmap"):
134            if hasattr(self, "_rmap"):
135                clone._rmap = self._rmap[:]
136            clone._tmap = self._tmap[:]
137            clone._cmap = self._cmap[:]
138        return clone
repeated: int | None
154    @property
155    def repeated(self) -> int | None:
156        """Get / set the number of times the row is repeated.
157
158        Always None when using the table API.
159
160        Return: int or None
161        """
162        repeated = self.get_attribute("table:number-rows-repeated")
163        if repeated is None:
164            return None
165        return int(repeated)

Get / set the number of times the row is repeated.

Always None when using the table API.

Return: int or None

style: str | None
191    @property
192    def style(self) -> str | None:
193        """Get /set the style of the row itself.
194
195        Return: str
196        """
197        return self.get_attribute("table:style-name")  # type: ignore

Get /set the style of the row itself.

Return: str

width: int
203    @property
204    def width(self) -> int:
205        """Get the number of expected cells in the row, i.e. addition
206        repetitions.
207
208        Return: int
209        """
210        try:
211            value = self._rmap[-1] + 1
212        except Exception:
213            value = 0
214        return value

Get the number of expected cells in the row, i.e. addition repetitions.

Return: int

def traverse( self, start: int | None = None, end: int | None = None) -> collections.abc.Iterator[Cell]:
219    def traverse(  # noqa: C901
220        self,
221        start: int | None = None,
222        end: int | None = None,
223    ) -> Iterator[Cell]:
224        """Yield as many cell elements as expected cells in the row, i.e.
225        expand repetitions by returning the same cell as many times as
226        necessary.
227
228            Arguments:
229
230                start -- int
231
232                end -- int
233
234        Copies are returned, use set_cell() to push them back.
235        """
236        idx = -1
237        before = -1
238        x = 0
239        cell: Cell
240        if start is None and end is None:
241            for juska in self._rmap:
242                idx += 1
243                if idx in self._indexes["_rmap"]:
244                    cell = self._indexes["_rmap"][idx]
245                else:
246                    cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
247                    if not isinstance(cell, Cell):
248                        raise TypeError(f"Not a cell: {cell!r}")
249                    self._indexes["_rmap"][idx] = cell
250                repeated = juska - before
251                before = juska
252                for _i in range(repeated or 1):
253                    # Return a copy without the now obsolete repetition
254                    if cell is None:
255                        cell = Cell()
256                    else:
257                        cell = cell.clone
258                        if repeated > 1:
259                            cell.repeated = None
260                    cell.y = self.y
261                    cell.x = x
262                    x += 1
263                    yield cell
264        else:
265            if start is None:
266                start = 0
267            start = max(0, start)
268            if end is None:
269                try:
270                    end = self._rmap[-1]
271                except Exception:
272                    end = -1
273            start_map = find_odf_idx(self._rmap, start)
274            if start_map is None:
275                return
276            if start_map > 0:
277                before = self._rmap[start_map - 1]
278            idx = start_map - 1
279            before = start - 1
280            x = start
281            for juska in self._rmap[start_map:]:
282                idx += 1
283                if idx in self._indexes["_rmap"]:
284                    cell = self._indexes["_rmap"][idx]
285                else:
286                    cell = self._get_element_idx2(_xpath_cell_idx, idx)  # type: ignore
287                    if not isinstance(cell, Cell):
288                        raise TypeError(f"Not a cell: {cell!r}")
289                    self._indexes["_rmap"][idx] = cell
290                repeated = juska - before
291                before = juska
292                for _i in range(repeated or 1):
293                    if x <= end:
294                        if cell is None:
295                            cell = Cell()
296                        else:
297                            cell = cell.clone
298                            if repeated > 1 or (x == start and start > 0):
299                                cell.repeated = None
300                        cell.y = self.y
301                        cell.x = x
302                        x += 1
303                        yield cell

Yield as many cell elements as expected cells in the row, i.e. expand repetitions by returning the same cell as many times as necessary.

Arguments:

    start -- int

    end -- int

Copies are returned, use set_cell() to push them back.

def get_cells( self, coord: str | tuple | None = None, style: str | None = None, content: str | None = None, cell_type: str | None = None) -> list[Cell]:
305    def get_cells(
306        self,
307        coord: str | tuple | None = None,
308        style: str | None = None,
309        content: str | None = None,
310        cell_type: str | None = None,
311    ) -> list[Cell]:
312        """Get the list of cells matching the criteria.
313
314        Filter by cell_type, with cell_type 'all' will retrieve cells of any
315        type, aka non empty cells.
316
317        Filter by coordinates will retrieve the amount of cells defined by
318        'coord', minus the other filters.
319
320        Arguments:
321
322            coord -- str or tuple of int : coordinates
323
324            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
325                         'currency', 'percentage' or 'all'
326
327            content -- str regex
328
329            style -- str
330
331        Return: list of Cell
332        """
333        # fixme : not clones ?
334        if coord:
335            x, z = self._translate_row_coordinates(coord)
336        else:
337            x = None
338            z = None
339        if cell_type:
340            cell_type = cell_type.lower().strip()
341        cells: list[Cell] = []
342        for cell in self.traverse(start=x, end=z):
343            # Filter the cells by cell_type
344            if cell_type:
345                ctype = cell.type
346                if not ctype or not (ctype == cell_type or cell_type == "all"):
347                    continue
348            # Filter the cells with the regex
349            if content and not cell.match(content):
350                continue
351            # Filter the cells with the style
352            if style and style != cell.style:
353                continue
354            cells.append(cell)
355        return cells

Get the list of cells matching the criteria.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells.

Filter by coordinates will retrieve the amount of cells defined by 'coord', minus the other filters.

Arguments:

coord -- str or tuple of int : coordinates

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

content -- str regex

style -- str

Return: list of Cell

def get_cell(self, x: int, clone: bool = True) -> Cell | None:
377    def get_cell(self, x: int, clone: bool = True) -> Cell | None:
378        """Get the cell at position "x" starting from 0. Alphabetical
379        positions like "D" are accepted.
380
381        A  copy is returned, use set_cell() to push it back.
382
383        Arguments:
384
385            x -- int or str
386
387        Return: Cell | None
388        """
389        x = self._translate_x_from_any(x)
390        cell = self._get_cell2(x, clone=clone)
391        if not cell:
392            return None
393        cell.y = self.y
394        cell.x = x
395        return cell

Get the cell at position "x" starting from 0. Alphabetical positions like "D" are accepted.

A copy is returned, use set_cell() to push it back.

Arguments:

x -- int or str

Return: Cell | None

def get_value( self, x: int | str, get_type: bool = False) -> typing.Any | tuple[typing.Any, str]:
397    def get_value(
398        self,
399        x: int | str,
400        get_type: bool = False,
401    ) -> Any | tuple[Any, str]:
402        """Shortcut to get the value of the cell at position "x".
403        If get_type is True, returns the tuples (value, ODF type).
404
405        If the cell is empty, returns None or (None, None)
406
407        See get_cell() and Cell.get_value().
408        """
409        if get_type:
410            x = self._translate_x_from_any(x)
411            cell = self._get_cell2_base(x)
412            if cell is None:
413                return (None, None)
414            return cell.get_value(get_type=get_type)
415        x = self._translate_x_from_any(x)
416        cell = self._get_cell2_base(x)
417        if cell is None:
418            return None
419        return cell.get_value()

Shortcut to get the value of the cell at position "x". If get_type is True, returns the tuples (value, ODF type).

If the cell is empty, returns None or (None, None)

See get_cell() and Cell.get_value().

def set_cell( self, x: int | str, cell: Cell | None = None, clone: bool = True) -> Cell:
421    def set_cell(
422        self,
423        x: int | str,
424        cell: Cell | None = None,
425        clone: bool = True,
426    ) -> Cell:
427        """Push the cell back in the row at position "x" starting from 0.
428        Alphabetical positions like "D" are accepted.
429
430        Arguments:
431
432            x -- int or str
433
434        returns the cell with x and y updated
435        """
436        cell_back: Cell
437        if cell is None:
438            cell = Cell()
439            repeated = 1
440            clone = False
441        else:
442            repeated = cell.repeated or 1
443        x = self._translate_x_from_any(x)
444        # Outside the defined row
445        diff = x - self.width
446        if diff == 0:
447            cell_back = self.append_cell(cell, _repeated=repeated, clone=clone)
448        elif diff > 0:
449            self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False)
450            cell_back = self.append_cell(cell, _repeated=repeated, clone=clone)
451        else:
452            # Inside the defined row
453            set_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap", clone=clone)
454            cell.x = x
455            cell.y = self.y
456            cell_back = cell
457        return cell_back

Push the cell back in the row at position "x" starting from 0. Alphabetical positions like "D" are accepted.

Arguments:

x -- int or str

returns the cell with x and y updated

def set_value( self, x: int | str, value: Any, style: str | None = None, cell_type: str | None = None, currency: str | None = None) -> None:
459    def set_value(
460        self,
461        x: int | str,
462        value: Any,
463        style: str | None = None,
464        cell_type: str | None = None,
465        currency: str | None = None,
466    ) -> None:
467        """Shortcut to set the value of the cell at position "x".
468
469        Arguments:
470
471            x -- int or str
472
473            value -- Python type
474
475            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
476                     'string' or 'time'
477
478            currency -- three-letter str
479
480            style -- str
481
482        See get_cell() and Cell.get_value().
483        """
484        self.set_cell(
485            x,
486            Cell(value, style=style, cell_type=cell_type, currency=currency),
487            clone=False,
488        )

Shortcut to set the value of the cell at position "x".

Arguments:

x -- int or str

value -- Python type

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
         'string' or 'time'

currency -- three-letter str

style -- str

See get_cell() and Cell.get_value().

def insert_cell( self, x: int | str, cell: Cell | None = None, clone: bool = True) -> Cell:
490    def insert_cell(
491        self,
492        x: int | str,
493        cell: Cell | None = None,
494        clone: bool = True,
495    ) -> Cell:
496        """Insert the given cell at position "x" starting from 0. If no cell
497        is given, an empty one is created.
498
499        Alphabetical positions like "D" are accepted.
500
501        Do not use when working on a table, use Table.insert_cell().
502
503        Arguments:
504
505            x -- int or str
506
507            cell -- Cell
508
509        returns the cell with x and y updated
510        """
511        cell_back: Cell
512        if cell is None:
513            cell = Cell()
514        x = self._translate_x_from_any(x)
515        # Outside the defined row
516        diff = x - self.width
517        if diff < 0:
518            insert_item_in_vault(x, cell, self, _xpath_cell_idx, "_rmap")
519            cell.x = x
520            cell.y = self.y
521            cell_back = cell
522        elif diff == 0:
523            cell_back = self.append_cell(cell, clone=clone)
524        else:
525            self.append_cell(Cell(repeated=diff), _repeated=diff, clone=False)
526            cell_back = self.append_cell(cell, clone=clone)
527        return cell_back

Insert the given cell at position "x" starting from 0. If no cell is given, an empty one is created.

Alphabetical positions like "D" are accepted.

Do not use when working on a table, use Table.insert_cell().

Arguments:

x -- int or str

cell -- Cell

returns the cell with x and y updated

def extend_cells( self, cells: collections.abc.Iterable[Cell] | None = None) -> None:
529    def extend_cells(self, cells: Iterable[Cell] | None = None) -> None:
530        if cells is None:
531            cells = []
532        self.extend(cells)
533        self._compute_row_cache()
def append_cell( self, cell: Cell | None = None, clone: bool = True, _repeated: int | None = None) -> Cell:
535    def append_cell(
536        self,
537        cell: Cell | None = None,
538        clone: bool = True,
539        _repeated: int | None = None,
540    ) -> Cell:
541        """Append the given cell at the end of the row. Repeated cells are
542        accepted. If no cell is given, an empty one is created.
543
544        Do not use when working on a table, use Table.append_cell().
545
546        Arguments:
547
548            cell -- Cell
549
550            _repeated -- (optional), repeated value of the row
551
552        returns the cell with x and y updated
553        """
554        if cell is None:
555            cell = Cell()
556            clone = False
557        if clone:
558            cell = cell.clone
559        self._append(cell)
560        if _repeated is None:
561            _repeated = cell.repeated or 1
562        self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated)
563        cell.x = self.width - 1
564        cell.y = self.y
565        return cell

Append the given cell at the end of the row. Repeated cells are accepted. If no cell is given, an empty one is created.

Do not use when working on a table, use Table.append_cell().

Arguments:

cell -- Cell

_repeated -- (optional), repeated value of the row

returns the cell with x and y updated

def append( self, cell: Cell | None = None, clone: bool = True, _repeated: int | None = None) -> Cell:
535    def append_cell(
536        self,
537        cell: Cell | None = None,
538        clone: bool = True,
539        _repeated: int | None = None,
540    ) -> Cell:
541        """Append the given cell at the end of the row. Repeated cells are
542        accepted. If no cell is given, an empty one is created.
543
544        Do not use when working on a table, use Table.append_cell().
545
546        Arguments:
547
548            cell -- Cell
549
550            _repeated -- (optional), repeated value of the row
551
552        returns the cell with x and y updated
553        """
554        if cell is None:
555            cell = Cell()
556            clone = False
557        if clone:
558            cell = cell.clone
559        self._append(cell)
560        if _repeated is None:
561            _repeated = cell.repeated or 1
562        self._rmap = insert_map_once(self._rmap, len(self._rmap), _repeated)
563        cell.x = self.width - 1
564        cell.y = self.y
565        return cell

Append the given cell at the end of the row. Repeated cells are accepted. If no cell is given, an empty one is created.

Do not use when working on a table, use Table.append_cell().

Arguments:

cell -- Cell

_repeated -- (optional), repeated value of the row

returns the cell with x and y updated

def delete_cell(self, x: int | str) -> None:
570    def delete_cell(self, x: int | str) -> None:
571        """Delete the cell at the given position "x" starting from 0.
572        Alphabetical positions like "D" are accepted.
573
574        Cells on the right will be shifted to the left. In a table, other
575        rows remain unaffected.
576
577        Arguments:
578
579            x -- int or str
580        """
581        x = self._translate_x_from_any(x)
582        if x >= self.width:
583            return
584        delete_item_in_vault(x, self, _xpath_cell_idx, "_rmap")

Delete the cell at the given position "x" starting from 0. Alphabetical positions like "D" are accepted.

Cells on the right will be shifted to the left. In a table, other rows remain unaffected.

Arguments:

x -- int or str
def get_values( self, coord: str | tuple | None = None, cell_type: str | None = None, complete: bool = False, get_type: bool = False) -> list[typing.Any | tuple[typing.Any, typing.Any]]:
586    def get_values(
587        self,
588        coord: str | tuple | None = None,
589        cell_type: str | None = None,
590        complete: bool = False,
591        get_type: bool = False,
592    ) -> list[Any | tuple[Any, Any]]:
593        """Shortcut to get the cell values in this row.
594
595        Filter by cell_type, with cell_type 'all' will retrieve cells of any
596        type, aka non empty cells.
597        If cell_type is used and complete is True, missing values are
598        replaced by None.
599        If cell_type is None, complete is always True : with no cell type
600        queried, get_values() returns None for each empty cell, the length
601        of the list is equal to the length of the row (depending on
602        coordinates use).
603
604        If get_type is True, returns a tuple (value, ODF type of value), or
605        (None, None) for empty cells if complete is True.
606
607        Filter by coordinates will retrieve the amount of cells defined by
608        coordinates with None for empty cells, except when using cell_type.
609
610
611        Arguments:
612
613            coord -- str or tuple of int : coordinates in row
614
615            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
616                         'currency', 'percentage' or 'all'
617
618            complete -- boolean
619
620            get_type -- boolean
621
622        Return: list of Python types, or list of tuples.
623        """
624        if coord:
625            x, z = self._translate_row_coordinates(coord)
626        else:
627            x = None
628            z = None
629        if cell_type:
630            cell_type = cell_type.lower().strip()
631            values: list[Any | tuple[Any, Any]] = []
632            for cell in self.traverse(start=x, end=z):
633                # Filter the cells by cell_type
634                ctype = cell.type
635                if not ctype or not (ctype == cell_type or cell_type == "all"):
636                    if complete:
637                        if get_type:
638                            values.append((None, None))
639                        else:
640                            values.append(None)
641                    continue
642                values.append(cell.get_value(get_type=get_type))
643            return values
644        else:
645            return [
646                cell.get_value(get_type=get_type)
647                for cell in self.traverse(start=x, end=z)
648            ]

Shortcut to get the cell values in this row.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type is used and complete is True, missing values are replaced by None. If cell_type is None, complete is always True : with no cell type queried, get_values() returns None for each empty cell, the length of the list is equal to the length of the row (depending on coordinates use).

If get_type is True, returns a tuple (value, ODF type of value), or (None, None) for empty cells if complete is True.

Filter by coordinates will retrieve the amount of cells defined by coordinates with None for empty cells, except when using cell_type.

Arguments:

coord -- str or tuple of int : coordinates in row

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: list of Python types, or list of tuples.

def set_cells( self, cells: list[Cell] | tuple[Cell] | None = None, start: int | str = 0, clone: bool = True) -> None:
650    def set_cells(
651        self,
652        cells: list[Cell] | tuple[Cell] | None = None,
653        start: int | str = 0,
654        clone: bool = True,
655    ) -> None:
656        """Set the cells in the row, from the 'start' column.
657        This method does not clear the row, use row.clear() before to start
658        with an empty row.
659
660        Arguments:
661
662            cells -- list of cells
663
664            start -- int or str
665        """
666        if cells is None:
667            cells = []
668        if start is None:
669            start = 0
670        else:
671            start = self._translate_x_from_any(start)
672        if start == 0 and clone is False and (len(cells) >= self.width):
673            self.clear()
674            self.extend_cells(cells)
675        else:
676            x = start
677            for cell in cells:
678                self.set_cell(x, cell, clone=clone)
679                if cell:
680                    x += cell.repeated or 1
681                else:
682                    x += 1

Set the cells in the row, from the 'start' column. This method does not clear the row, use row.clear() before to start with an empty row.

Arguments:

cells -- list of cells

start -- int or str
def set_values( self, values: list[typing.Any], start: int | str = 0, style: str | None = None, cell_type: str | None = None, currency: str | None = None) -> None:
684    def set_values(
685        self,
686        values: list[Any],
687        start: int | str = 0,
688        style: str | None = None,
689        cell_type: str | None = None,
690        currency: str | None = None,
691    ) -> None:
692        """Shortcut to set the value of cells in the row, from the 'start'
693        column vith values.
694        This method does not clear the row, use row.clear() before to start
695        with an empty row.
696
697        Arguments:
698
699            values -- list of Python types
700
701            start -- int or str
702
703            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
704                         'currency' or 'percentage'
705
706            currency -- three-letter str
707
708            style -- cell style
709        """
710        # fixme : if values n, n+ are same, use repeat
711        if start is None:
712            start = 0
713        else:
714            start = self._translate_x_from_any(start)
715        if start == 0 and (len(values) >= self.width):
716            self.clear()
717            cells = [
718                Cell(value, style=style, cell_type=cell_type, currency=currency)
719                for value in values
720            ]
721            self.extend_cells(cells)
722        else:
723            x = start
724            for value in values:
725                self.set_cell(
726                    x,
727                    Cell(value, style=style, cell_type=cell_type, currency=currency),
728                    clone=False,
729                )
730                x += 1

Shortcut to set the value of cells in the row, from the 'start' column vith values. This method does not clear the row, use row.clear() before to start with an empty row.

Arguments:

values -- list of Python types

start -- int or str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency' or 'percentage'

currency -- three-letter str

style -- cell style
def rstrip(self, aggressive: bool = False) -> None:
732    def rstrip(self, aggressive: bool = False) -> None:
733        """Remove *in-place* empty cells at the right of the row. An empty
734        cell has no value but can have style. If "aggressive" is True, style
735        is ignored.
736
737        Arguments:
738
739            aggressive -- bool
740        """
741        for cell in reversed(self._get_cells()):
742            if not cell.is_empty(aggressive=aggressive):  # type: ignore
743                break
744            self.delete(cell)
745        self._compute_row_cache()
746        self._indexes["_rmap"] = {}

Remove in-place empty cells at the right of the row. An empty cell has no value but can have style. If "aggressive" is True, style is ignored.

Arguments:

aggressive -- bool
def minimized_width(self) -> int:
761    def minimized_width(self) -> int:
762        """Return the length of the row if the last repeated sequence is
763        reduced to one.
764
765        Return: int
766        """
767        idx_repeated_seq = self.elements_repeated_sequence(
768            _xpath_cell, "table:number-columns-repeated"
769        )
770        repeated = [item[1] for item in idx_repeated_seq]
771        if repeated:
772            cell = self.last_cell()
773            if cell is not None and cell.is_empty(aggressive=True):
774                repeated[-1] = 1
775            min_width = sum(repeated)
776        else:
777            min_width = 1
778        self._compute_row_cache()
779        self._indexes["_rmap"] = {}
780        return min_width

Return the length of the row if the last repeated sequence is reduced to one.

Return: int

def last_cell(self) -> Cell | None:
782    def last_cell(self) -> Cell | None:
783        """Return the las cell of the row.
784
785        Return Cell | None
786        """
787        try:
788            return self._get_cells()[-1]  # type: ignore
789        except IndexError:
790            return None

Return the las cell of the row.

Return Cell | None

def force_width(self, width: int) -> None:
792    def force_width(self, width: int) -> None:
793        """Change the repeated property of the last cell of the row
794        to comply with the required max width.
795
796        Arguments:
797
798            width -- int
799        """
800        cell = self.last_cell()
801        if cell is None or not cell.is_empty(aggressive=True):
802            return
803        repeated = cell.repeated
804        if repeated is None:
805            return
806        # empty repeated cell
807        delta = self._current_length() - width
808        if delta > 0:
809            cell._set_repeated(repeated - delta)
810            self._compute_row_cache()

Change the repeated property of the last cell of the row to comply with the required max width.

Arguments:

width -- int
def is_empty(self, aggressive: bool = False) -> bool:
812    def is_empty(self, aggressive: bool = False) -> bool:
813        """Return whether every cell in the row has no value or the value
814        evaluates to False (empty string), and no style.
815
816        If aggressive is True, empty cells with style are considered empty.
817
818        Arguments:
819
820            aggressive -- bool
821
822        Return: bool
823        """
824        return all(cell.is_empty(aggressive=aggressive) for cell in self._get_cells())  # type: ignore

Return whether every cell in the row has no value or the value evaluates to False (empty string), and no style.

If aggressive is True, empty cells with style are considered empty.

Arguments:

aggressive -- bool

Return: bool

Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
get_between
insert
extend
delete
replace_element
strip_elements
strip_tags
xpath
clear
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class RowGroup(odfdo.Element):
133class RowGroup(Element):
134    """ "table:table-row-group" group rows with common properties."""
135
136    # TODO
137    _tag = "table:table-row-group"
138    _caching = True
139
140    def __init__(
141        self,
142        height: int | None = None,
143        width: int | None = None,
144        **kwargs: Any,
145    ) -> None:
146        """Create a group of rows, optionnaly filled with "height" number of
147        rows, of "width" cells each.
148
149        Row group bear style information applied to a series of rows.
150
151        Arguments:
152
153            height -- int
154
155            width -- int
156        """
157        super().__init__(**kwargs)
158        if self._do_init and height is not None:
159            for _i in range(height):
160                row = Row(width=width)
161                self.append(row)

"table:table-row-group" group rows with common properties.

RowGroup(height: int | None = None, width: int | None = None, **kwargs: Any)
140    def __init__(
141        self,
142        height: int | None = None,
143        width: int | None = None,
144        **kwargs: Any,
145    ) -> None:
146        """Create a group of rows, optionnaly filled with "height" number of
147        rows, of "width" cells each.
148
149        Row group bear style information applied to a series of rows.
150
151        Arguments:
152
153            height -- int
154
155            width -- int
156        """
157        super().__init__(**kwargs)
158        if self._do_init and height is not None:
159            for _i in range(height):
160                row = Row(width=width)
161                self.append(row)

Create a group of rows, optionnaly filled with "height" number of rows, of "width" cells each.

Row group bear style information applied to a series of rows.

Arguments:

height -- int

width -- int
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Section(odfdo.Element):
32class Section(Element):
33    """ODF section "text:section"
34
35    Arguments:
36
37        style -- str
38
39        name -- str
40    """
41
42    _tag = "text:section"
43    _properties = (
44        PropDef("style", "text:style-name"),
45        PropDef("name", "text:name"),
46    )
47
48    def __init__(
49        self,
50        style: str | None = None,
51        name: str | None = None,
52        **kwargs: Any,
53    ) -> None:
54        super().__init__(**kwargs)
55        if self._do_init:
56            if style:
57                self.style = style
58            if name:
59                self.name = name
60
61    def get_formatted_text(self, context: dict | None = None) -> str:
62        result = [element.get_formatted_text(context) for element in self.children]
63        result.append("\n")
64        return "".join(result)

ODF section "text:section"

Arguments:

style -- str

name -- str
Section(style: str | None = None, name: str | None = None, **kwargs: Any)
48    def __init__(
49        self,
50        style: str | None = None,
51        name: str | None = None,
52        **kwargs: Any,
53    ) -> None:
54        super().__init__(**kwargs)
55        if self._do_init:
56            if style:
57                self.style = style
58            if name:
59                self.name = name
def get_formatted_text(self, context: dict | None = None) -> str:
61    def get_formatted_text(self, context: dict | None = None) -> str:
62        result = [element.get_formatted_text(context) for element in self.children]
63        result.append("\n")
64        return "".join(result)

This function should return a beautiful version of the text.

style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Spacer(odfdo.Element):
169class Spacer(Element):
170    """This element shall be used to represent the second and all following “ “
171    (U+0020, SPACE) characters in a sequence of “ “ (U+0020, SPACE) characters.
172    Note: It is not an error if the character preceding the element is not a
173    white space character, but it is good practice to use this element only for
174    the second and all following SPACE characters in a sequence.
175    """
176
177    _tag = "text:s"
178    _properties: tuple[PropDef, ...] = (PropDef("number", "text:c"),)
179
180    def __init__(self, number: int = 1, **kwargs: Any):
181        """
182        Arguments:
183
184            number -- int
185        """
186        super().__init__(**kwargs)
187        if self._do_init:
188            self.number = str(number)

This element shall be used to represent the second and all following “ “ (U+0020, SPACE) characters in a sequence of “ “ (U+0020, SPACE) characters. Note: It is not an error if the character preceding the element is not a white space character, but it is good practice to use this element only for the second and all following SPACE characters in a sequence.

Spacer(number: int = 1, **kwargs: Any)
180    def __init__(self, number: int = 1, **kwargs: Any):
181        """
182        Arguments:
183
184            number -- int
185        """
186        super().__init__(**kwargs)
187        if self._do_init:
188            self.number = str(number)

Arguments:

number -- int
number: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Span(odfdo.Paragraph):
799class Span(Paragraph):
800    """Create a span element "text:span" of the given style containing the optional
801    given text.
802    """
803
804    _tag = "text:span"
805    _properties = (
806        PropDef("style", "text:style-name"),
807        PropDef("class_names", "text:class-names"),
808    )
809
810    def __init__(
811        self,
812        text: str | None = None,
813        style: str | None = None,
814        **kwargs: Any,
815    ) -> None:
816        """
817        Arguments:
818
819            text -- str
820
821            style -- str
822        """
823        super().__init__(**kwargs)
824        if self._do_init:
825            if text:
826                self.text = text
827            if style:
828                self.style = style

Create a span element "text:span" of the given style containing the optional given text.

Span(text: str | None = None, style: str | None = None, **kwargs: Any)
810    def __init__(
811        self,
812        text: str | None = None,
813        style: str | None = None,
814        **kwargs: Any,
815    ) -> None:
816        """
817        Arguments:
818
819            text -- str
820
821            style -- str
822        """
823        super().__init__(**kwargs)
824        if self._do_init:
825            if text:
826                self.text = text
827            if style:
828                self.style = style

Arguments:

text -- str

style -- str
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
class_names: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Paragraph
insert_note
insert_annotation
insert_annotation_end
set_reference_mark
set_reference_mark_end
insert_variable
set_span
remove_spans
remove_span
insert_reference
set_bookmark
odfdo.paragraph_base.ParagraphBase
get_formatted_text
append_plain_text
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Style(odfdo.Element):
285class Style(Element):
286    """Style class for all these tags:
287
288    'style:style'
289    'number:date-style',
290    'number:number-style',
291    'number:percentage-style',
292    'number:time-style'
293    'style:font-face',
294    'style:master-page',
295    'style:page-layout',
296    'style:presentation-page-layout',
297    'text:list-style',
298    'text:outline-style',
299    'style:tab-stops',
300    ...
301    """
302
303    _properties: tuple[PropDef, ...] = (
304        PropDef("page_layout", "style:page-layout-name", "master-page"),
305        PropDef("next_style", "style:next-style-name", "master-page"),
306        PropDef("name", "style:name"),
307        PropDef("parent_style", "style:parent-style-name"),
308        PropDef("display_name", "style:display-name"),
309        PropDef("svg_font_family", "svg:font-family"),
310        PropDef("font_family_generic", "style:font-family-generic"),
311        PropDef("font_pitch", "style:font-pitch"),
312        PropDef("text_style", "text:style-name"),
313        PropDef("master_page", "style:master-page-name", "paragraph"),
314        PropDef("master_page", "style:master-page-name", "paragraph"),
315        PropDef("master_page", "style:master-page-name", "paragraph"),
316        # style:tab-stop
317        PropDef("style_type", "style:type"),
318        PropDef("leader_style", "style:leader-style"),
319        PropDef("leader_text", "style:leader-text"),
320        PropDef("style_position", "style:position"),
321        PropDef("leader_text", "style:position"),
322    )
323
324    def __init__(  # noqa: C901
325        self,
326        family: str | None = None,
327        name: str | None = None,
328        display_name: str | None = None,
329        parent_style: str | None = None,
330        # Where properties apply
331        area: str | None = None,
332        # For family 'text':
333        color: str | tuple | None = None,
334        background_color: str | tuple | None = None,
335        italic: bool = False,
336        bold: bool = False,
337        # For family 'paragraph'
338        master_page: str | None = None,
339        # For family 'master-page'
340        page_layout: str | None = None,
341        next_style: str | None = None,
342        # For family 'table-cell'
343        data_style: str | None = None,  # unused
344        border: str | None = None,
345        border_top: str | None = None,
346        border_right: str | None = None,
347        border_bottom: str | None = None,
348        border_left: str | None = None,
349        padding: str | None = None,
350        padding_top: str | None = None,
351        padding_bottom: str | None = None,
352        padding_left: str | None = None,
353        padding_right: str | None = None,
354        shadow: str | None = None,
355        # For family 'table-row'
356        height: str | None = None,
357        use_optimal_height: bool = False,
358        # For family 'table-column'
359        width: str | None = None,
360        break_before: str | None = None,
361        break_after: str | None = None,
362        # For family 'graphic'
363        min_height: str | None = None,
364        # For family 'font-face'
365        font_name: str | None = None,
366        font_family: str | None = None,
367        font_family_generic: str | None = None,
368        font_pitch: str = "variable",
369        # Every other property
370        **kwargs: Any,
371    ) -> None:
372        """Create a style of the given family. The name is not mandatory at this
373        point but will become required when inserting in a document as a common
374        style.
375
376        The display name is the name the user sees in an office application.
377
378        The parent_style is the name of the style this style will inherit from.
379
380        To set properties, pass them as keyword arguments. The area properties
381        apply to is optional and defaults to the family.
382
383        Arguments:
384
385            family -- 'paragraph', 'text', 'section', 'table', 'table-column',
386                      'table-row', 'table-cell', 'table-page', 'chart',
387                      'drawing-page', 'graphic', 'presentation',
388                      'control', 'ruby', 'list', 'number', 'page-layout'
389                      'font-face', or 'master-page'
390
391            name -- str
392
393            display_name -- str
394
395            parent_style -- str
396
397            area -- str
398
399        'text' Properties:
400
401            italic -- bool
402
403            bold -- bool
404
405        'paragraph' Properties:
406
407            master_page -- str
408
409        'master-page' Properties:
410
411            page_layout -- str
412
413            next_style -- str
414
415        'table-cell' Properties:
416
417            border, border_top, border_right, border_bottom, border_left -- str,
418            e.g. "0.002cm solid #000000" or 'none'
419
420            padding, padding_top, padding_right, padding_bottom, padding_left -- str,
421            e.g. "0.002cm" or 'none'
422
423            shadow -- str, e.g. "#808080 0.176cm 0.176cm"
424
425        'table-row' Properties:
426
427            height -- str, e.g. '5cm'
428
429            use_optimal_height -- bool
430
431        'table-column' Properties:
432
433            width -- str, e.g. '5cm'
434
435            break_before -- 'page', 'column' or 'auto'
436
437            break_after -- 'page', 'column' or 'auto'
438        """
439        self._family: str | None = None
440        tag_or_elem = kwargs.get("tag_or_elem", None)
441        if tag_or_elem is None:
442            family = to_str(family)
443            if family not in FAMILY_MAPPING:
444                raise ValueError("Unknown family value: %s" % family)
445            kwargs["tag"] = FAMILY_MAPPING[family]
446        super().__init__(**kwargs)
447        if self._do_init and family not in SUBCLASSED_STYLES:
448            kwargs.pop("tag", None)
449            kwargs.pop("tag_or_elem", None)
450            self.family = family  # relevant test made by property
451            # Common attributes
452            if name:
453                self.name = name
454            if display_name:
455                self.display_name = display_name
456            if parent_style:
457                self.parent_style = parent_style
458            # Paragraph
459            if family == "paragraph":
460                if master_page:
461                    self.master_page = master_page
462            # Master Page
463            elif family == "master-page":
464                if page_layout:
465                    self.page_layout = page_layout
466                if next_style:
467                    self.next_style = next_style
468            # Font face
469            elif family == "font-face":
470                if not font_name:
471                    raise ValueError("A font_name is required for 'font-face' style")
472                self.set_font(
473                    font_name,
474                    family=font_family,
475                    family_generic=font_family_generic,
476                    pitch=font_pitch,
477                )
478            # Properties
479            if area is None:
480                area = family
481            area = to_str(area)
482            # Text
483            if area == "text":
484                if color:
485                    kwargs["fo:color"] = color
486                if background_color:
487                    kwargs["fo:background-color"] = background_color
488                if italic:
489                    kwargs["fo:font-style"] = "italic"
490                    kwargs["style:font-style-asian"] = "italic"
491                    kwargs["style:font-style-complex"] = "italic"
492                if bold:
493                    kwargs["fo:font-weight"] = "bold"
494                    kwargs["style:font-weight-asian"] = "bold"
495                    kwargs["style:font-weight-complex"] = "bold"
496            # Table cell
497            elif area == "table-cell":
498                if border:
499                    kwargs["fo:border"] = border
500                elif border_top or border_right or border_bottom or border_left:
501                    kwargs["fo:border-top"] = border_top or "none"
502                    kwargs["fo:border-right"] = border_right or "none"
503                    kwargs["fo:border-bottom"] = border_bottom or "none"
504                    kwargs["fo:border-left"] = border_left or "none"
505                else:  # no border_top, ... neither border are defined
506                    pass  # left untouched
507                if padding:
508                    kwargs["fo:padding"] = padding
509                elif padding_top or padding_right or padding_bottom or padding_left:
510                    kwargs["fo:padding-top"] = padding_top or "none"
511                    kwargs["fo:padding-right"] = padding_right or "none"
512                    kwargs["fo:padding-bottom"] = padding_bottom or "none"
513                    kwargs["fo:padding-left"] = padding_left or "none"
514                else:  # no border_top, ... neither border are defined
515                    pass  # left untouched
516                if shadow:
517                    kwargs["style:shadow"] = shadow
518                if background_color:
519                    kwargs["fo:background-color"] = background_color
520            # Table row
521            elif area == "table-row":
522                if height:
523                    kwargs["style:row-height"] = height
524                if use_optimal_height:
525                    kwargs["style:use-optimal-row-height"] = Boolean.encode(
526                        use_optimal_height
527                    )
528                if background_color:
529                    kwargs["fo:background-color"] = background_color
530            # Table column
531            elif area == "table-column":
532                if width:
533                    kwargs["style:column-width"] = width
534                if break_before:
535                    kwargs["fo:break-before"] = break_before
536                if break_after:
537                    kwargs["fo:break-after"] = break_after
538            # Graphic
539            elif area == "graphic":
540                if min_height:
541                    kwargs["fo:min-height"] = min_height
542            # Every other properties
543            if kwargs:
544                self.set_properties(kwargs, area=area)
545
546    @property
547    def family(self) -> str | None:
548        if self._family is None:
549            self._family = FALSE_FAMILY_MAP_REVERSE.get(
550                self.tag, self.get_attribute_string("style:family")
551            )
552        return self._family
553
554    @family.setter
555    def family(self, family: str | None) -> None:
556        self._family = family
557        if family in FAMILY_ODF_STD and self.tag == "style:style":
558            self.set_attribute("style:family", family)
559
560    def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None:
561        """Get the mapping of all properties of this style. By default the
562        properties of the same family, e.g. a paragraph style and its
563        paragraph properties. Specify the area to get the text properties of
564        a paragraph style for example.
565
566        Arguments:
567
568            area -- str
569
570        Return: dict
571        """
572        if area is None:
573            area = self.family
574        element = self.get_element(f"style:{area}-properties")
575        if element is None:
576            return None
577        properties: dict[str, str | dict] = element.attributes  # type: ignore
578        # Nested properties are nested dictionaries
579        for child in element.children:
580            properties[child.tag] = child.attributes
581        return properties
582
583    def set_properties(  # noqa: C901
584        self,
585        properties: dict[str, str | dict] | None = None,
586        style: Style | None = None,
587        area: str | None = None,
588        **kwargs: Any,
589    ) -> None:
590        """Set the properties of the "area" type of this style. Properties
591        are given either as a dict or as named arguments (or both). The area
592        is identical to the style family by default. If the properties
593        element is missing, it is created.
594
595        Instead of properties, you can pass a style with properties of the
596        same area. These will be copied.
597
598        Arguments:
599
600            properties -- dict
601
602            style -- Style
603
604            area -- 'paragraph', 'text'...
605        """
606        if properties is None:
607            properties = {}
608        if area is None:
609            if isinstance(self.family, bool):
610                area = None
611            else:
612                area = self.family
613        element = self.get_element(f"style:{area}-properties")
614        if element is None:
615            element = Element.from_tag(f"style:{area}-properties")
616            self.append(element)
617        if properties or kwargs:
618            properties = _expand_properties_dict(_merge_dicts(properties, kwargs))
619        elif style is not None:
620            properties = style.get_properties(area=area)
621            if properties is None:
622                return
623        if properties is None:
624            return
625        for key, value in properties.items():
626            if value is None:
627                element.del_attribute(key)
628            elif isinstance(value, (str, bool, tuple)):
629                element.set_attribute(key, value)
630            else:
631                pass
632
633    def del_properties(
634        self,
635        properties: list[str] | None = None,
636        area: str | None = None,
637    ) -> None:
638        """Delete the given properties, either by list argument or
639        positional argument (or both). Remove only from the given area,
640        identical to the style family by default.
641
642        Arguments:
643
644            properties -- list
645
646            area -- str
647        """
648        if properties is None:
649            properties = []
650        if area is None:
651            area = self.family
652        element = self.get_element(f"style:{area}-properties")
653        if element is None:
654            raise ValueError(
655                f"properties element is inexistent for: style:{area}-properties"
656            )
657        for key in _expand_properties_list(properties):
658            element.del_attribute(key)
659
660    def set_background(  # noqa: C901
661        self,
662        color: str | None = None,
663        url: str | None = None,
664        position: str | None = "center",
665        repeat: str | None = None,
666        opacity: str | None = None,
667        filter: str | None = None,  # noqa: A002
668    ) -> None:
669        """Set the background color of a text style, or the background color
670        or image of a paragraph style or page layout.
671
672        With no argument, remove any existing background.
673
674        The position is one or two of 'center', 'left', 'right', 'top' or
675        'bottom'.
676
677        The repeat is 'no-repeat', 'repeat' or 'stretch'.
678
679        The opacity is a percentage integer (not a string with the '%s' sign)
680
681        The filter is an application-specific filter name defined elsewhere.
682
683        Though this method is defined on the base style class, it will raise
684        an error if the style type is not compatible.
685
686        Arguments:
687
688            color -- '#rrggbb'
689
690            url -- str
691
692            position -- str
693
694            repeat -- str
695
696            opacity -- int
697
698            filter -- str
699        """
700        family = self.family
701        if family not in {
702            "text",
703            "paragraph",
704            "page-layout",
705            "section",
706            "table",
707            "table-row",
708            "table-cell",
709            "graphic",
710        }:
711            raise TypeError("No background support for this family")
712        if url is not None and family == "text":
713            raise TypeError("No background image for text styles")
714        properties = self.get_element(f"style:{family}-properties")
715        bg_image: BackgroundImage | None = None
716        if properties is not None:
717            bg_image = properties.get_element("style:background-image")  # type:ignore
718        # Erasing
719        if color is None and url is None:
720            if properties is None:
721                return
722            properties.del_attribute("fo:background-color")
723            if bg_image is not None:
724                properties.delete(bg_image)
725            return
726        # Add the properties if necessary
727        if properties is None:
728            properties = Element.from_tag(f"style:{family}-properties")
729            self.append(properties)
730        # Add the color...
731        if color:
732            properties.set_attribute("fo:background-color", color)
733            if bg_image is not None:
734                properties.delete(bg_image)
735        # ... or the background
736        elif url:
737            properties.set_attribute("fo:background-color", "transparent")
738            if bg_image is None:
739                bg_image = Element.from_tag("style:background-image")  # type:ignore
740                properties.append(bg_image)  # type:ignore
741            bg_image.url = url  # type:ignore
742            if position:
743                bg_image.position = position  # type:ignore
744            if repeat:
745                bg_image.repeat = repeat  # type:ignore
746            if opacity:
747                bg_image.opacity = opacity  # type:ignore
748            if filter:
749                bg_image.filter = filter  # type:ignore
750
751    # list-style only:
752
753    def get_level_style(self, level: int) -> Style | None:
754        if self.family != "list":
755            return None
756        level_styles = (
757            "(text:list-level-style-number"
758            "|text:list-level-style-bullet"
759            "|text:list-level-style-image)"
760        )
761        return self._filtered_element(level_styles, 0, level=level)  # type: ignore
762
763    def set_level_style(  # noqa: C901
764        self,
765        level: int,
766        num_format: str | None = None,
767        bullet_char: str | None = None,
768        url: str | None = None,
769        display_levels: int | None = None,
770        prefix: str | None = None,
771        suffix: str | None = None,
772        start_value: int | None = None,
773        style: str | None = None,
774        clone: Style | None = None,
775    ) -> Style | None:
776        """
777        Arguments:
778
779            level -- int
780
781            num_format (for number) -- int
782
783            bullet_char (for bullet) -- str
784
785            url (for image) -- str
786
787            display_levels -- int
788
789            prefix -- str
790
791            suffix -- str
792
793            start_value -- int
794
795            style -- str
796
797            clone -- List Style
798
799        Return:
800            level_style created
801        """
802        if self.family != "list":
803            return None
804        # Expected name
805        if num_format is not None:
806            level_style_name = "text:list-level-style-number"
807        elif bullet_char is not None:
808            level_style_name = "text:list-level-style-bullet"
809        elif url is not None:
810            level_style_name = "text:list-level-style-image"
811        elif clone is not None:
812            level_style_name = clone.tag
813        else:
814            raise ValueError("unknown level style type")
815        was_created = False
816        # Cloning or reusing an existing element
817        level_style: Style | None = None
818        if clone is not None:
819            level_style = clone.clone  # type: ignore
820            was_created = True
821        else:
822            level_style = self.get_level_style(level)
823            if level_style is None:
824                level_style = Element.from_tag(level_style_name)  # type: ignore
825                was_created = True
826        if level_style is None:
827            return None
828        # Transmute if the type changed
829        if level_style.tag != level_style_name:
830            print("Warn: different style", level_style_name, level_style.tag)
831            level_style.tag = level_style_name
832        # Set the level
833        level_style.set_attribute("text:level", str(level))
834        # Set the main attribute
835        if num_format is not None:
836            level_style.set_attribute("fo:num-format", num_format)
837        elif bullet_char is not None:
838            level_style.set_attribute("text:bullet-char", bullet_char)
839        elif url is not None:
840            level_style.set_attribute("xlink:href", url)
841        # Set attributes
842        if prefix:
843            level_style.set_attribute("style:num-prefix", prefix)
844        if suffix:
845            level_style.set_attribute("style:num-suffix", suffix)
846        if display_levels:
847            level_style.set_attribute("text:display-levels", str(display_levels))
848        if start_value:
849            level_style.set_attribute("text:start-value", str(start_value))
850        if style:
851            level_style.text_style = style  # type: ignore
852        # Commit the creation
853        if was_created:
854            self.append(level_style)
855        return level_style
856
857    # page-layout only:
858
859    def get_header_style(self) -> Element | None:
860        if self.family != "page-layout":
861            return None
862        return self.get_element("style:header-style")
863
864    def set_header_style(self, new_style: Style) -> None:
865        if self.family != "page-layout":
866            return
867        header_style = self.get_header_style()
868        if header_style is not None:
869            self.delete(header_style)
870        self.append(new_style)
871
872    def get_footer_style(self) -> Style | None:
873        if self.family != "page-layout":
874            return None
875        return self.get_element("style:footer-style")  # type: ignore
876
877    def set_footer_style(self, new_style: Style) -> None:
878        if self.family != "page-layout":
879            return
880        footer_style = self.get_footer_style()
881        if footer_style is not None:
882            self.delete(footer_style)
883        self.append(new_style)
884
885    # master-page only:
886
887    def _set_header_or_footer(
888        self,
889        text_or_element: str | Element | list[Element | str],
890        name: str = "header",
891        style: str = "Header",
892    ) -> None:
893        if name == "header":
894            header_or_footer = self.get_page_header()
895        else:
896            header_or_footer = self.get_page_footer()
897        if header_or_footer is None:
898            header_or_footer = Element.from_tag("style:" + name)
899            self.append(header_or_footer)
900        else:
901            header_or_footer.clear()
902        if (
903            isinstance(text_or_element, Element)
904            and text_or_element.tag == f"style:{name}"
905        ):
906            # Already a header or footer?
907            self.delete(header_or_footer)
908            self.append(text_or_element)
909            return
910        if isinstance(text_or_element, (Element, str)):
911            elem_list: list[Element | str] = [text_or_element]
912        else:
913            elem_list = text_or_element
914        for item in elem_list:
915            if isinstance(item, str):
916                paragraph = Element.from_tag("text:p")
917                paragraph.style = style  # type: ignore
918                header_or_footer.append(paragraph)
919            elif isinstance(item, Element):
920                header_or_footer.append(item)
921
922    def get_page_header(self) -> Element | None:
923        """Get the element that contains the header contents.
924
925        If None, no header was set.
926        """
927        if self.family != "master-page":
928            return None
929        return self.get_element("style:header")
930
931    def set_page_header(
932        self,
933        text_or_element: str | Element | list[Element | str],
934    ) -> None:
935        """Create or replace the header by the given content. It can already
936        be a complete header.
937
938        If you only want to update the existing header, get it and use the
939        API.
940
941        Arguments:
942
943            text_or_element -- str or Element or a list of them
944        """
945        if self.family != "master-page":
946            return None
947        self._set_header_or_footer(text_or_element)
948
949    def get_page_footer(self) -> Element | None:
950        """Get the element that contains the footer contents.
951
952        If None, no footer was set.
953        """
954        if self.family != "master-page":
955            return None
956        return self.get_element("style:footer")
957
958    def set_page_footer(
959        self,
960        text_or_element: str | Element | list[Element | str],
961    ) -> None:
962        """Create or replace the footer by the given content. It can already
963        be a complete footer.
964
965        If you only want to update the existing footer, get it and use the
966        API.
967
968        Arguments:
969
970            text_or_element -- str or Element or a list of them
971        """
972        if self.family != "master-page":
973            return None
974        self._set_header_or_footer(text_or_element, name="footer", style="Footer")
975
976    # font-face only:
977
978    def set_font(
979        self,
980        name: str,
981        family: str | None = None,
982        family_generic: str | None = None,
983        pitch: str = "variable",
984    ) -> None:
985        if self.family != "font-face":
986            return
987        self.name = name
988        if family is None:
989            family = name
990        self.svg_font_family = f'"{family}"'
991        if family_generic is not None:
992            self.font_family_generic = family_generic
993        self.font_pitch = pitch

Style class for all these tags:

'style:style' 'number:date-style', 'number:number-style', 'number:percentage-style', 'number:time-style' 'style:font-face', 'style:master-page', 'style:page-layout', 'style:presentation-page-layout', 'text:list-style', 'text:outline-style', 'style:tab-stops', ...

Style( family: str | None = None, name: str | None = None, display_name: str | None = None, parent_style: str | None = None, area: str | None = None, color: str | tuple | None = None, background_color: str | tuple | None = None, italic: bool = False, bold: bool = False, master_page: str | None = None, page_layout: str | None = None, next_style: str | None = None, data_style: str | None = None, border: str | None = None, border_top: str | None = None, border_right: str | None = None, border_bottom: str | None = None, border_left: str | None = None, padding: str | None = None, padding_top: str | None = None, padding_bottom: str | None = None, padding_left: str | None = None, padding_right: str | None = None, shadow: str | None = None, height: str | None = None, use_optimal_height: bool = False, width: str | None = None, break_before: str | None = None, break_after: str | None = None, min_height: str | None = None, font_name: str | None = None, font_family: str | None = None, font_family_generic: str | None = None, font_pitch: str = 'variable', **kwargs: Any)
324    def __init__(  # noqa: C901
325        self,
326        family: str | None = None,
327        name: str | None = None,
328        display_name: str | None = None,
329        parent_style: str | None = None,
330        # Where properties apply
331        area: str | None = None,
332        # For family 'text':
333        color: str | tuple | None = None,
334        background_color: str | tuple | None = None,
335        italic: bool = False,
336        bold: bool = False,
337        # For family 'paragraph'
338        master_page: str | None = None,
339        # For family 'master-page'
340        page_layout: str | None = None,
341        next_style: str | None = None,
342        # For family 'table-cell'
343        data_style: str | None = None,  # unused
344        border: str | None = None,
345        border_top: str | None = None,
346        border_right: str | None = None,
347        border_bottom: str | None = None,
348        border_left: str | None = None,
349        padding: str | None = None,
350        padding_top: str | None = None,
351        padding_bottom: str | None = None,
352        padding_left: str | None = None,
353        padding_right: str | None = None,
354        shadow: str | None = None,
355        # For family 'table-row'
356        height: str | None = None,
357        use_optimal_height: bool = False,
358        # For family 'table-column'
359        width: str | None = None,
360        break_before: str | None = None,
361        break_after: str | None = None,
362        # For family 'graphic'
363        min_height: str | None = None,
364        # For family 'font-face'
365        font_name: str | None = None,
366        font_family: str | None = None,
367        font_family_generic: str | None = None,
368        font_pitch: str = "variable",
369        # Every other property
370        **kwargs: Any,
371    ) -> None:
372        """Create a style of the given family. The name is not mandatory at this
373        point but will become required when inserting in a document as a common
374        style.
375
376        The display name is the name the user sees in an office application.
377
378        The parent_style is the name of the style this style will inherit from.
379
380        To set properties, pass them as keyword arguments. The area properties
381        apply to is optional and defaults to the family.
382
383        Arguments:
384
385            family -- 'paragraph', 'text', 'section', 'table', 'table-column',
386                      'table-row', 'table-cell', 'table-page', 'chart',
387                      'drawing-page', 'graphic', 'presentation',
388                      'control', 'ruby', 'list', 'number', 'page-layout'
389                      'font-face', or 'master-page'
390
391            name -- str
392
393            display_name -- str
394
395            parent_style -- str
396
397            area -- str
398
399        'text' Properties:
400
401            italic -- bool
402
403            bold -- bool
404
405        'paragraph' Properties:
406
407            master_page -- str
408
409        'master-page' Properties:
410
411            page_layout -- str
412
413            next_style -- str
414
415        'table-cell' Properties:
416
417            border, border_top, border_right, border_bottom, border_left -- str,
418            e.g. "0.002cm solid #000000" or 'none'
419
420            padding, padding_top, padding_right, padding_bottom, padding_left -- str,
421            e.g. "0.002cm" or 'none'
422
423            shadow -- str, e.g. "#808080 0.176cm 0.176cm"
424
425        'table-row' Properties:
426
427            height -- str, e.g. '5cm'
428
429            use_optimal_height -- bool
430
431        'table-column' Properties:
432
433            width -- str, e.g. '5cm'
434
435            break_before -- 'page', 'column' or 'auto'
436
437            break_after -- 'page', 'column' or 'auto'
438        """
439        self._family: str | None = None
440        tag_or_elem = kwargs.get("tag_or_elem", None)
441        if tag_or_elem is None:
442            family = to_str(family)
443            if family not in FAMILY_MAPPING:
444                raise ValueError("Unknown family value: %s" % family)
445            kwargs["tag"] = FAMILY_MAPPING[family]
446        super().__init__(**kwargs)
447        if self._do_init and family not in SUBCLASSED_STYLES:
448            kwargs.pop("tag", None)
449            kwargs.pop("tag_or_elem", None)
450            self.family = family  # relevant test made by property
451            # Common attributes
452            if name:
453                self.name = name
454            if display_name:
455                self.display_name = display_name
456            if parent_style:
457                self.parent_style = parent_style
458            # Paragraph
459            if family == "paragraph":
460                if master_page:
461                    self.master_page = master_page
462            # Master Page
463            elif family == "master-page":
464                if page_layout:
465                    self.page_layout = page_layout
466                if next_style:
467                    self.next_style = next_style
468            # Font face
469            elif family == "font-face":
470                if not font_name:
471                    raise ValueError("A font_name is required for 'font-face' style")
472                self.set_font(
473                    font_name,
474                    family=font_family,
475                    family_generic=font_family_generic,
476                    pitch=font_pitch,
477                )
478            # Properties
479            if area is None:
480                area = family
481            area = to_str(area)
482            # Text
483            if area == "text":
484                if color:
485                    kwargs["fo:color"] = color
486                if background_color:
487                    kwargs["fo:background-color"] = background_color
488                if italic:
489                    kwargs["fo:font-style"] = "italic"
490                    kwargs["style:font-style-asian"] = "italic"
491                    kwargs["style:font-style-complex"] = "italic"
492                if bold:
493                    kwargs["fo:font-weight"] = "bold"
494                    kwargs["style:font-weight-asian"] = "bold"
495                    kwargs["style:font-weight-complex"] = "bold"
496            # Table cell
497            elif area == "table-cell":
498                if border:
499                    kwargs["fo:border"] = border
500                elif border_top or border_right or border_bottom or border_left:
501                    kwargs["fo:border-top"] = border_top or "none"
502                    kwargs["fo:border-right"] = border_right or "none"
503                    kwargs["fo:border-bottom"] = border_bottom or "none"
504                    kwargs["fo:border-left"] = border_left or "none"
505                else:  # no border_top, ... neither border are defined
506                    pass  # left untouched
507                if padding:
508                    kwargs["fo:padding"] = padding
509                elif padding_top or padding_right or padding_bottom or padding_left:
510                    kwargs["fo:padding-top"] = padding_top or "none"
511                    kwargs["fo:padding-right"] = padding_right or "none"
512                    kwargs["fo:padding-bottom"] = padding_bottom or "none"
513                    kwargs["fo:padding-left"] = padding_left or "none"
514                else:  # no border_top, ... neither border are defined
515                    pass  # left untouched
516                if shadow:
517                    kwargs["style:shadow"] = shadow
518                if background_color:
519                    kwargs["fo:background-color"] = background_color
520            # Table row
521            elif area == "table-row":
522                if height:
523                    kwargs["style:row-height"] = height
524                if use_optimal_height:
525                    kwargs["style:use-optimal-row-height"] = Boolean.encode(
526                        use_optimal_height
527                    )
528                if background_color:
529                    kwargs["fo:background-color"] = background_color
530            # Table column
531            elif area == "table-column":
532                if width:
533                    kwargs["style:column-width"] = width
534                if break_before:
535                    kwargs["fo:break-before"] = break_before
536                if break_after:
537                    kwargs["fo:break-after"] = break_after
538            # Graphic
539            elif area == "graphic":
540                if min_height:
541                    kwargs["fo:min-height"] = min_height
542            # Every other properties
543            if kwargs:
544                self.set_properties(kwargs, area=area)

Create a style of the given family. The name is not mandatory at this point but will become required when inserting in a document as a common style.

The display name is the name the user sees in an office application.

The parent_style is the name of the style this style will inherit from.

To set properties, pass them as keyword arguments. The area properties apply to is optional and defaults to the family.

Arguments:

family -- 'paragraph', 'text', 'section', 'table', 'table-column',
          'table-row', 'table-cell', 'table-page', 'chart',
          'drawing-page', 'graphic', 'presentation',
          'control', 'ruby', 'list', 'number', 'page-layout'
          'font-face', or 'master-page'

name -- str

display_name -- str

parent_style -- str

area -- str

'text' Properties:

italic -- bool

bold -- bool

'paragraph' Properties:

master_page -- str

'master-page' Properties:

page_layout -- str

next_style -- str

'table-cell' Properties:

border, border_top, border_right, border_bottom, border_left -- str,
e.g. "0.002cm solid #000000" or 'none'

padding, padding_top, padding_right, padding_bottom, padding_left -- str,
e.g. "0.002cm" or 'none'

shadow -- str, e.g. "#808080 0.176cm 0.176cm"

'table-row' Properties:

height -- str, e.g. '5cm'

use_optimal_height -- bool

'table-column' Properties:

width -- str, e.g. '5cm'

break_before -- 'page', 'column' or 'auto'

break_after -- 'page', 'column' or 'auto'
family: str | None
546    @property
547    def family(self) -> str | None:
548        if self._family is None:
549            self._family = FALSE_FAMILY_MAP_REVERSE.get(
550                self.tag, self.get_attribute_string("style:family")
551            )
552        return self._family
def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None:
560    def get_properties(self, area: str | None = None) -> dict[str, str | dict] | None:
561        """Get the mapping of all properties of this style. By default the
562        properties of the same family, e.g. a paragraph style and its
563        paragraph properties. Specify the area to get the text properties of
564        a paragraph style for example.
565
566        Arguments:
567
568            area -- str
569
570        Return: dict
571        """
572        if area is None:
573            area = self.family
574        element = self.get_element(f"style:{area}-properties")
575        if element is None:
576            return None
577        properties: dict[str, str | dict] = element.attributes  # type: ignore
578        # Nested properties are nested dictionaries
579        for child in element.children:
580            properties[child.tag] = child.attributes
581        return properties

Get the mapping of all properties of this style. By default the properties of the same family, e.g. a paragraph style and its paragraph properties. Specify the area to get the text properties of a paragraph style for example.

Arguments:

area -- str

Return: dict

def set_properties( self, properties: dict[str, str | dict] | None = None, style: Style | None = None, area: str | None = None, **kwargs: Any) -> None:
583    def set_properties(  # noqa: C901
584        self,
585        properties: dict[str, str | dict] | None = None,
586        style: Style | None = None,
587        area: str | None = None,
588        **kwargs: Any,
589    ) -> None:
590        """Set the properties of the "area" type of this style. Properties
591        are given either as a dict or as named arguments (or both). The area
592        is identical to the style family by default. If the properties
593        element is missing, it is created.
594
595        Instead of properties, you can pass a style with properties of the
596        same area. These will be copied.
597
598        Arguments:
599
600            properties -- dict
601
602            style -- Style
603
604            area -- 'paragraph', 'text'...
605        """
606        if properties is None:
607            properties = {}
608        if area is None:
609            if isinstance(self.family, bool):
610                area = None
611            else:
612                area = self.family
613        element = self.get_element(f"style:{area}-properties")
614        if element is None:
615            element = Element.from_tag(f"style:{area}-properties")
616            self.append(element)
617        if properties or kwargs:
618            properties = _expand_properties_dict(_merge_dicts(properties, kwargs))
619        elif style is not None:
620            properties = style.get_properties(area=area)
621            if properties is None:
622                return
623        if properties is None:
624            return
625        for key, value in properties.items():
626            if value is None:
627                element.del_attribute(key)
628            elif isinstance(value, (str, bool, tuple)):
629                element.set_attribute(key, value)
630            else:
631                pass

Set the properties of the "area" type of this style. Properties are given either as a dict or as named arguments (or both). The area is identical to the style family by default. If the properties element is missing, it is created.

Instead of properties, you can pass a style with properties of the same area. These will be copied.

Arguments:

properties -- dict

style -- Style

area -- 'paragraph', 'text'...
def del_properties( self, properties: list[str] | None = None, area: str | None = None) -> None:
633    def del_properties(
634        self,
635        properties: list[str] | None = None,
636        area: str | None = None,
637    ) -> None:
638        """Delete the given properties, either by list argument or
639        positional argument (or both). Remove only from the given area,
640        identical to the style family by default.
641
642        Arguments:
643
644            properties -- list
645
646            area -- str
647        """
648        if properties is None:
649            properties = []
650        if area is None:
651            area = self.family
652        element = self.get_element(f"style:{area}-properties")
653        if element is None:
654            raise ValueError(
655                f"properties element is inexistent for: style:{area}-properties"
656            )
657        for key in _expand_properties_list(properties):
658            element.del_attribute(key)

Delete the given properties, either by list argument or positional argument (or both). Remove only from the given area, identical to the style family by default.

Arguments:

properties -- list

area -- str
def set_background( self, color: str | None = None, url: str | None = None, position: str | None = 'center', repeat: str | None = None, opacity: str | None = None, filter: str | None = None) -> None:
660    def set_background(  # noqa: C901
661        self,
662        color: str | None = None,
663        url: str | None = None,
664        position: str | None = "center",
665        repeat: str | None = None,
666        opacity: str | None = None,
667        filter: str | None = None,  # noqa: A002
668    ) -> None:
669        """Set the background color of a text style, or the background color
670        or image of a paragraph style or page layout.
671
672        With no argument, remove any existing background.
673
674        The position is one or two of 'center', 'left', 'right', 'top' or
675        'bottom'.
676
677        The repeat is 'no-repeat', 'repeat' or 'stretch'.
678
679        The opacity is a percentage integer (not a string with the '%s' sign)
680
681        The filter is an application-specific filter name defined elsewhere.
682
683        Though this method is defined on the base style class, it will raise
684        an error if the style type is not compatible.
685
686        Arguments:
687
688            color -- '#rrggbb'
689
690            url -- str
691
692            position -- str
693
694            repeat -- str
695
696            opacity -- int
697
698            filter -- str
699        """
700        family = self.family
701        if family not in {
702            "text",
703            "paragraph",
704            "page-layout",
705            "section",
706            "table",
707            "table-row",
708            "table-cell",
709            "graphic",
710        }:
711            raise TypeError("No background support for this family")
712        if url is not None and family == "text":
713            raise TypeError("No background image for text styles")
714        properties = self.get_element(f"style:{family}-properties")
715        bg_image: BackgroundImage | None = None
716        if properties is not None:
717            bg_image = properties.get_element("style:background-image")  # type:ignore
718        # Erasing
719        if color is None and url is None:
720            if properties is None:
721                return
722            properties.del_attribute("fo:background-color")
723            if bg_image is not None:
724                properties.delete(bg_image)
725            return
726        # Add the properties if necessary
727        if properties is None:
728            properties = Element.from_tag(f"style:{family}-properties")
729            self.append(properties)
730        # Add the color...
731        if color:
732            properties.set_attribute("fo:background-color", color)
733            if bg_image is not None:
734                properties.delete(bg_image)
735        # ... or the background
736        elif url:
737            properties.set_attribute("fo:background-color", "transparent")
738            if bg_image is None:
739                bg_image = Element.from_tag("style:background-image")  # type:ignore
740                properties.append(bg_image)  # type:ignore
741            bg_image.url = url  # type:ignore
742            if position:
743                bg_image.position = position  # type:ignore
744            if repeat:
745                bg_image.repeat = repeat  # type:ignore
746            if opacity:
747                bg_image.opacity = opacity  # type:ignore
748            if filter:
749                bg_image.filter = filter  # type:ignore

Set the background color of a text style, or the background color or image of a paragraph style or page layout.

With no argument, remove any existing background.

The position is one or two of 'center', 'left', 'right', 'top' or 'bottom'.

The repeat is 'no-repeat', 'repeat' or 'stretch'.

The opacity is a percentage integer (not a string with the '%s' sign)

The filter is an application-specific filter name defined elsewhere.

Though this method is defined on the base style class, it will raise an error if the style type is not compatible.

Arguments:

color -- '#rrggbb'

url -- str

position -- str

repeat -- str

opacity -- int

filter -- str
def get_level_style(self, level: int) -> Style | None:
753    def get_level_style(self, level: int) -> Style | None:
754        if self.family != "list":
755            return None
756        level_styles = (
757            "(text:list-level-style-number"
758            "|text:list-level-style-bullet"
759            "|text:list-level-style-image)"
760        )
761        return self._filtered_element(level_styles, 0, level=level)  # type: ignore
def set_level_style( self, level: int, num_format: str | None = None, bullet_char: str | None = None, url: str | None = None, display_levels: int | None = None, prefix: str | None = None, suffix: str | None = None, start_value: int | None = None, style: str | None = None, clone: Style | None = None) -> Style | None:
763    def set_level_style(  # noqa: C901
764        self,
765        level: int,
766        num_format: str | None = None,
767        bullet_char: str | None = None,
768        url: str | None = None,
769        display_levels: int | None = None,
770        prefix: str | None = None,
771        suffix: str | None = None,
772        start_value: int | None = None,
773        style: str | None = None,
774        clone: Style | None = None,
775    ) -> Style | None:
776        """
777        Arguments:
778
779            level -- int
780
781            num_format (for number) -- int
782
783            bullet_char (for bullet) -- str
784
785            url (for image) -- str
786
787            display_levels -- int
788
789            prefix -- str
790
791            suffix -- str
792
793            start_value -- int
794
795            style -- str
796
797            clone -- List Style
798
799        Return:
800            level_style created
801        """
802        if self.family != "list":
803            return None
804        # Expected name
805        if num_format is not None:
806            level_style_name = "text:list-level-style-number"
807        elif bullet_char is not None:
808            level_style_name = "text:list-level-style-bullet"
809        elif url is not None:
810            level_style_name = "text:list-level-style-image"
811        elif clone is not None:
812            level_style_name = clone.tag
813        else:
814            raise ValueError("unknown level style type")
815        was_created = False
816        # Cloning or reusing an existing element
817        level_style: Style | None = None
818        if clone is not None:
819            level_style = clone.clone  # type: ignore
820            was_created = True
821        else:
822            level_style = self.get_level_style(level)
823            if level_style is None:
824                level_style = Element.from_tag(level_style_name)  # type: ignore
825                was_created = True
826        if level_style is None:
827            return None
828        # Transmute if the type changed
829        if level_style.tag != level_style_name:
830            print("Warn: different style", level_style_name, level_style.tag)
831            level_style.tag = level_style_name
832        # Set the level
833        level_style.set_attribute("text:level", str(level))
834        # Set the main attribute
835        if num_format is not None:
836            level_style.set_attribute("fo:num-format", num_format)
837        elif bullet_char is not None:
838            level_style.set_attribute("text:bullet-char", bullet_char)
839        elif url is not None:
840            level_style.set_attribute("xlink:href", url)
841        # Set attributes
842        if prefix:
843            level_style.set_attribute("style:num-prefix", prefix)
844        if suffix:
845            level_style.set_attribute("style:num-suffix", suffix)
846        if display_levels:
847            level_style.set_attribute("text:display-levels", str(display_levels))
848        if start_value:
849            level_style.set_attribute("text:start-value", str(start_value))
850        if style:
851            level_style.text_style = style  # type: ignore
852        # Commit the creation
853        if was_created:
854            self.append(level_style)
855        return level_style

Arguments:

level -- int

num_format (for number) -- int

bullet_char (for bullet) -- str

url (for image) -- str

display_levels -- int

prefix -- str

suffix -- str

start_value -- int

style -- str

clone -- List Style

Return: level_style created

def get_header_style(self) -> Element | None:
859    def get_header_style(self) -> Element | None:
860        if self.family != "page-layout":
861            return None
862        return self.get_element("style:header-style")
def set_header_style(self, new_style: Style) -> None:
864    def set_header_style(self, new_style: Style) -> None:
865        if self.family != "page-layout":
866            return
867        header_style = self.get_header_style()
868        if header_style is not None:
869            self.delete(header_style)
870        self.append(new_style)
def get_page_header(self) -> Element | None:
922    def get_page_header(self) -> Element | None:
923        """Get the element that contains the header contents.
924
925        If None, no header was set.
926        """
927        if self.family != "master-page":
928            return None
929        return self.get_element("style:header")

Get the element that contains the header contents.

If None, no header was set.

def set_page_header( self, text_or_element: str | Element | list[Element | str]) -> None:
931    def set_page_header(
932        self,
933        text_or_element: str | Element | list[Element | str],
934    ) -> None:
935        """Create or replace the header by the given content. It can already
936        be a complete header.
937
938        If you only want to update the existing header, get it and use the
939        API.
940
941        Arguments:
942
943            text_or_element -- str or Element or a list of them
944        """
945        if self.family != "master-page":
946            return None
947        self._set_header_or_footer(text_or_element)

Create or replace the header by the given content. It can already be a complete header.

If you only want to update the existing header, get it and use the API.

Arguments:

text_or_element -- str or Element or a list of them
def set_font( self, name: str, family: str | None = None, family_generic: str | None = None, pitch: str = 'variable') -> None:
978    def set_font(
979        self,
980        name: str,
981        family: str | None = None,
982        family_generic: str | None = None,
983        pitch: str = "variable",
984    ) -> None:
985        if self.family != "font-face":
986            return
987        self.name = name
988        if family is None:
989            family = name
990        self.svg_font_family = f'"{family}"'
991        if family_generic is not None:
992            self.font_family_generic = family_generic
993        self.font_pitch = pitch
page_layout: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
next_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
parent_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
display_name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
svg_font_family: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
font_family_generic: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
font_pitch: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
text_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
master_page: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_text: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style_position: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Styles(odfdo.XmlPart):
 58class Styles(XmlPart):
 59    def _get_style_contexts(
 60        self, family: str, automatic: bool = False
 61    ) -> list[Element]:
 62        if automatic:
 63            return [self.get_element("//office:automatic-styles")]
 64        if not family:
 65            # All possibilities
 66            return [
 67                self.get_element("//office:automatic-styles"),
 68                self.get_element("//office:styles"),
 69                self.get_element("//office:master-styles"),
 70                self.get_element("//office:font-face-decls"),
 71            ]
 72        queries = CONTEXT_MAPPING.get(family)
 73        if queries is None:
 74            raise ValueError(f"unknown family: {family}")
 75        # print('q:', queries)
 76        return [self.get_element(query) for query in queries]
 77
 78    def get_styles(self, family: str = "", automatic: bool = False) -> list[Element]:
 79        """Return the list of styles in the Content part, optionally limited
 80        to the given family, optionaly limited to automatic styles.
 81
 82        Arguments:
 83
 84            family -- str
 85
 86            automatic -- bool
 87
 88        Return: list of Style
 89        """
 90        result = []
 91        for context in self._get_style_contexts(family, automatic=automatic):
 92            if context is None:
 93                continue
 94            # print('-ctx----', automatic)
 95            # print(context.tag)
 96            # print(context.__class__)
 97            # print(context.serialize())
 98            result.extend(context.get_styles(family=family))
 99        return result
100
101    def get_style(
102        self,
103        family: str,
104        name_or_element: str | Style | None = None,
105        display_name: str | None = None,
106    ) -> Style | None:
107        """Return the style uniquely identified by the name/family pair. If
108        the argument is already a style object, it will return it.
109
110        If the name is None, the default style is fetched.
111
112        If the name is not the internal name but the name you gave in the
113        desktop application, use display_name instead.
114
115        Arguments:
116
117            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
118                      'number', 'page-layout', 'master-page'
119
120            name_or_element -- str, odf_style or None
121
122            display_name -- str or None
123
124        Return: odf_style or None if not found
125        """
126        for context in self._get_style_contexts(family):
127            if context is None:
128                continue
129            style = context.get_style(
130                family,
131                name_or_element=name_or_element,
132                display_name=display_name,
133            )
134            if style is not None:
135                return style  # type: ignore
136        return None
137
138    def get_master_pages(self) -> list[Element]:
139        query = make_xpath_query("descendant::style:master-page")
140        return self.get_elements(query)  # type:ignore
141
142    def get_master_page(self, position: int = 0) -> Element | None:
143        results = self.get_master_pages()
144        try:
145            return results[position]
146        except IndexError:
147            return None

Representation of an XML part.

Abstraction of the XML library behind.

def get_styles( self, family: str = '', automatic: bool = False) -> list[Element]:
78    def get_styles(self, family: str = "", automatic: bool = False) -> list[Element]:
79        """Return the list of styles in the Content part, optionally limited
80        to the given family, optionaly limited to automatic styles.
81
82        Arguments:
83
84            family -- str
85
86            automatic -- bool
87
88        Return: list of Style
89        """
90        result = []
91        for context in self._get_style_contexts(family, automatic=automatic):
92            if context is None:
93                continue
94            # print('-ctx----', automatic)
95            # print(context.tag)
96            # print(context.__class__)
97            # print(context.serialize())
98            result.extend(context.get_styles(family=family))
99        return result

Return the list of styles in the Content part, optionally limited to the given family, optionaly limited to automatic styles.

Arguments:

family -- str

automatic -- bool

Return: list of Style

def get_style( self, family: str, name_or_element: str | Style | None = None, display_name: str | None = None) -> Style | None:
101    def get_style(
102        self,
103        family: str,
104        name_or_element: str | Style | None = None,
105        display_name: str | None = None,
106    ) -> Style | None:
107        """Return the style uniquely identified by the name/family pair. If
108        the argument is already a style object, it will return it.
109
110        If the name is None, the default style is fetched.
111
112        If the name is not the internal name but the name you gave in the
113        desktop application, use display_name instead.
114
115        Arguments:
116
117            family -- 'paragraph', 'text',  'graphic', 'table', 'list',
118                      'number', 'page-layout', 'master-page'
119
120            name_or_element -- str, odf_style or None
121
122            display_name -- str or None
123
124        Return: odf_style or None if not found
125        """
126        for context in self._get_style_contexts(family):
127            if context is None:
128                continue
129            style = context.get_style(
130                family,
131                name_or_element=name_or_element,
132                display_name=display_name,
133            )
134            if style is not None:
135                return style  # type: ignore
136        return None

Return the style uniquely identified by the name/family pair. If the argument is already a style object, it will return it.

If the name is None, the default style is fetched.

If the name is not the internal name but the name you gave in the desktop application, use display_name instead.

Arguments:

family -- 'paragraph', 'text',  'graphic', 'table', 'list',
          'number', 'page-layout', 'master-page'

name_or_element -- str, odf_style or None

display_name -- str or None

Return: odf_style or None if not found

def get_master_pages(self) -> list[Element]:
138    def get_master_pages(self) -> list[Element]:
139        query = make_xpath_query("descendant::style:master-page")
140        return self.get_elements(query)  # type:ignore
def get_master_page(self, position: int = 0) -> Element | None:
142    def get_master_page(self, position: int = 0) -> Element | None:
143        results = self.get_master_pages()
144        try:
145            return results[position]
146        except IndexError:
147            return None
class TOC(odfdo.Element):
168class TOC(Element):
169    """Table of content.
170    The "text:table-of-content" element represents a table of contents for a
171    document. The items that can be listed in a table of contents are:
172      - Headings (as defined by the outline structure of the document), up to
173        a selected level.
174      - Table of contents index marks.
175      - Paragraphs formatted with specified paragraph styles.
176
177
178    Implementation:
179    Default parameters are what most people use: protected from manual
180    modifications and not limited in title levels.
181
182    The name is mandatory and derived automatically from the title if not
183    given. Provide one in case of a conflict with other TOCs in the same
184    document.
185
186    The "text:table-of-content" element has the following attributes:
187    text:name, text:protected, text:protection-key,
188    text:protection-key-digest-algorithm, text:style-name and xml:id.
189
190    Arguments:
191
192        title -- str
193
194        name -- str
195
196        protected -- bool
197
198        outline_level -- int
199
200        style -- str
201
202        title_style -- str
203
204        entry_style -- str
205    """
206
207    _tag = "text:table-of-content"
208    _properties = (
209        PropDef("name", "text:name"),
210        PropDef("style", "text:style-name"),
211        PropDef("xml_id", "xml:id"),
212        PropDef("protected", "text:protected"),
213        PropDef("protection_key", "text:protection-key"),
214        PropDef(
215            "protection_key_digest_algorithm", "text:protection-key-digest-algorithm"
216        ),
217    )
218
219    def __init__(
220        self,
221        title: str = "Table of Contents",
222        name: str | None = None,
223        protected: bool = True,
224        outline_level: int = 0,
225        style: str | None = None,
226        title_style: str = "Contents_20_Heading",
227        entry_style: str = "Contents_20_%d",
228        **kwargs: Any,
229    ) -> None:
230        super().__init__(**kwargs)
231        if self._do_init:
232            if style:
233                self.style = style
234            if protected:
235                self.protected = protected
236            if name is None:
237                self.name = f"{title}1"
238            # Create the source template
239            toc_source = self.create_toc_source(
240                title, outline_level, title_style, entry_style
241            )
242            self.append(toc_source)
243            # Create the index body automatically with the index title
244            if title:
245                # This style is in the template document
246                self.set_toc_title(title, text_style=title_style)
247
248    @staticmethod
249    def create_toc_source(
250        title: str,
251        outline_level: int,
252        title_style: str,
253        entry_style: str,
254    ) -> Element:
255        toc_source = Element.from_tag("text:table-of-content-source")
256        toc_source.set_attribute("text:outline-level", str(outline_level))
257        if title:
258            title_template = IndexTitleTemplate()
259            if title_style:
260                # This style is in the template document
261                title_template.style = title_style
262            title_template.text = title
263            toc_source.append(title_template)
264        for level in range(1, 11):
265            template = TocEntryTemplate(outline_level=level)
266            if entry_style:
267                template.style = entry_style % level
268            toc_source.append(template)
269        return toc_source
270
271    def __str__(self) -> str:
272        return self.get_formatted_text()
273
274    def get_formatted_text(self, context: dict | None = None) -> str:
275        index_body = self.get_element("text:index-body")
276
277        if index_body is None:
278            return ""
279        if context is None:
280            context = {}
281        if context.get("rst_mode"):
282            return "\n.. contents::\n\n"
283
284        result = []
285        for element in index_body.children:
286            if element.tag == "text:index-title":
287                for child_element in element.children:
288                    result.append(child_element.get_formatted_text(context).strip())
289            else:
290                result.append(element.get_formatted_text(context).strip())
291        return "\n".join(result)
292
293    @property
294    def outline_level(self) -> int | None:
295        source = self.get_element("text:table-of-content-source")
296        if source is None:
297            return None
298        return source.get_attribute_integer("text:outline-level")
299
300    @outline_level.setter
301    def outline_level(self, level: int) -> None:
302        source = self.get_element("text:table-of-content-source")
303        if source is None:
304            source = Element.from_tag("text:table-of-content-source")
305            self.insert(source, FIRST_CHILD)
306        source.set_attribute("text:outline-level", str(level))
307
308    @property
309    def body(self) -> Element | None:
310        return self.get_element("text:index-body")
311
312    @body.setter
313    def body(self, body: Element | None = None) -> Element | None:
314        old_body = self.body
315        if old_body is not None:
316            self.delete(old_body)
317        if body is None:
318            body = Element.from_tag("text:index-body")
319        self.append(body)
320        return body
321
322    def get_title(self) -> str:
323        index_body = self.body
324        if index_body is None:
325            return ""
326        index_title = index_body.get_element(IndexTitle._tag)
327        if index_title is None:
328            return ""
329        return index_title.text_content
330
331    def set_toc_title(
332        self,
333        title: str,
334        style: str | None = None,
335        text_style: str | None = None,
336    ) -> None:
337        index_body = self.body
338        if index_body is None:
339            self.body = None
340            index_body = self.body
341        index_title = index_body.get_element(IndexTitle._tag)  # type: ignore
342        if index_title is None:
343            name = f"{self.name}_Head"
344            index_title = IndexTitle(
345                name=name, style=style, title_text=title, text_style=text_style
346            )
347            index_body.append(index_title)  # type: ignore
348        else:
349            if style:
350                index_title.style = style  # type: ignore
351            paragraph = index_title.get_paragraph()
352            if paragraph is None:
353                paragraph = Paragraph()
354                index_title.append(paragraph)
355            if text_style:
356                paragraph.style = text_style  # type: ignore
357            paragraph.text = title
358
359    @staticmethod
360    def _header_numbering(level_indexes: dict[int, int], level: int) -> str:
361        """Return the header hierarchical number (like "1.2.3.")."""
362        numbers: list[int] = []
363        # before header level
364        for idx in range(1, level):
365            numbers.append(level_indexes.setdefault(idx, 1))
366        # header level
367        index = level_indexes.get(level, 0) + 1
368        level_indexes[level] = index
369        numbers.append(index)
370        # after header level
371        idx = level + 1
372        while idx in level_indexes:
373            del level_indexes[idx]
374            idx += 1
375        return ".".join(str(x) for x in numbers) + "."
376
377    def fill(  # noqa: C901
378        self,
379        document: Document | None = None,
380        use_default_styles: bool = True,
381    ) -> None:
382        """Fill the TOC with the titles found in the document. A TOC is not
383        contextual so it will catch all titles before and after its insertion.
384        If the TOC is not attached to a document, attach it beforehand or
385        provide one as argument.
386
387        For having a pretty TOC, let use_default_styles by default.
388
389        Arguments:
390
391            document -- Document
392
393            use_default_styles -- bool
394        """
395        # Find the body
396        if document is not None:
397            body: Element | None = document.body
398        else:
399            body = self.document_body
400        if body is None:
401            raise ValueError("The TOC must be related to a document somehow")
402
403        # Save the title
404        index_body = self.body
405        title = index_body.get_element("text:index-title")  # type: ignore
406
407        # Clean the old index-body
408        self.body = None
409        index_body = self.body
410
411        # Restore the title
412        if title and str(title):
413            index_body.insert(title, position=0)  # type: ignore
414
415        # Insert default TOC style
416        if use_default_styles:
417            automatic_styles = body.get_element("//office:automatic-styles")
418            if isinstance(automatic_styles, Element):
419                for level in range(1, 11):
420                    if (
421                        automatic_styles.get_style(
422                            "paragraph", _toc_entry_style_name(level)
423                        )
424                        is None
425                    ):
426                        level_style = default_toc_level_style(level)
427                        automatic_styles.append(level_style)
428
429        # Auto-fill the index
430        outline_level = self.outline_level or 10
431        level_indexes: dict[int, int] = {}
432        for header in body.get_headers():
433            level = header.get_attribute_integer("text:outline-level") or 0
434            if level is None or level > outline_level:
435                continue
436            number_str = self._header_numbering(level_indexes, level)
437            # Make the title with "1.2.3. Title" format
438            paragraph = Paragraph(f"{number_str} {header}")
439            if use_default_styles:
440                paragraph.style = _toc_entry_style_name(level)
441            index_body.append(paragraph)  # type: ignore

Table of content. The "text:table-of-content" element represents a table of contents for a document. The items that can be listed in a table of contents are:

  • Headings (as defined by the outline structure of the document), up to a selected level.
  • Table of contents index marks.
  • Paragraphs formatted with specified paragraph styles.

Implementation: Default parameters are what most people use: protected from manual modifications and not limited in title levels.

The name is mandatory and derived automatically from the title if not given. Provide one in case of a conflict with other TOCs in the same document.

The "text:table-of-content" element has the following attributes: text:name, text:protected, text:protection-key, text:protection-key-digest-algorithm, text:style-name and xml:id.

Arguments:

title -- str

name -- str

protected -- bool

outline_level -- int

style -- str

title_style -- str

entry_style -- str
TOC( title: str = 'Table of Contents', name: str | None = None, protected: bool = True, outline_level: int = 0, style: str | None = None, title_style: str = 'Contents_20_Heading', entry_style: str = 'Contents_20_%d', **kwargs: Any)
219    def __init__(
220        self,
221        title: str = "Table of Contents",
222        name: str | None = None,
223        protected: bool = True,
224        outline_level: int = 0,
225        style: str | None = None,
226        title_style: str = "Contents_20_Heading",
227        entry_style: str = "Contents_20_%d",
228        **kwargs: Any,
229    ) -> None:
230        super().__init__(**kwargs)
231        if self._do_init:
232            if style:
233                self.style = style
234            if protected:
235                self.protected = protected
236            if name is None:
237                self.name = f"{title}1"
238            # Create the source template
239            toc_source = self.create_toc_source(
240                title, outline_level, title_style, entry_style
241            )
242            self.append(toc_source)
243            # Create the index body automatically with the index title
244            if title:
245                # This style is in the template document
246                self.set_toc_title(title, text_style=title_style)
@staticmethod
def create_toc_source( title: str, outline_level: int, title_style: str, entry_style: str) -> Element:
248    @staticmethod
249    def create_toc_source(
250        title: str,
251        outline_level: int,
252        title_style: str,
253        entry_style: str,
254    ) -> Element:
255        toc_source = Element.from_tag("text:table-of-content-source")
256        toc_source.set_attribute("text:outline-level", str(outline_level))
257        if title:
258            title_template = IndexTitleTemplate()
259            if title_style:
260                # This style is in the template document
261                title_template.style = title_style
262            title_template.text = title
263            toc_source.append(title_template)
264        for level in range(1, 11):
265            template = TocEntryTemplate(outline_level=level)
266            if entry_style:
267                template.style = entry_style % level
268            toc_source.append(template)
269        return toc_source
def get_formatted_text(self, context: dict | None = None) -> str:
274    def get_formatted_text(self, context: dict | None = None) -> str:
275        index_body = self.get_element("text:index-body")
276
277        if index_body is None:
278            return ""
279        if context is None:
280            context = {}
281        if context.get("rst_mode"):
282            return "\n.. contents::\n\n"
283
284        result = []
285        for element in index_body.children:
286            if element.tag == "text:index-title":
287                for child_element in element.children:
288                    result.append(child_element.get_formatted_text(context).strip())
289            else:
290                result.append(element.get_formatted_text(context).strip())
291        return "\n".join(result)

This function should return a beautiful version of the text.

outline_level: int | None
293    @property
294    def outline_level(self) -> int | None:
295        source = self.get_element("text:table-of-content-source")
296        if source is None:
297            return None
298        return source.get_attribute_integer("text:outline-level")
body: Element | None
308    @property
309    def body(self) -> Element | None:
310        return self.get_element("text:index-body")
def get_title(self) -> str:
322    def get_title(self) -> str:
323        index_body = self.body
324        if index_body is None:
325            return ""
326        index_title = index_body.get_element(IndexTitle._tag)
327        if index_title is None:
328            return ""
329        return index_title.text_content
def set_toc_title( self, title: str, style: str | None = None, text_style: str | None = None) -> None:
331    def set_toc_title(
332        self,
333        title: str,
334        style: str | None = None,
335        text_style: str | None = None,
336    ) -> None:
337        index_body = self.body
338        if index_body is None:
339            self.body = None
340            index_body = self.body
341        index_title = index_body.get_element(IndexTitle._tag)  # type: ignore
342        if index_title is None:
343            name = f"{self.name}_Head"
344            index_title = IndexTitle(
345                name=name, style=style, title_text=title, text_style=text_style
346            )
347            index_body.append(index_title)  # type: ignore
348        else:
349            if style:
350                index_title.style = style  # type: ignore
351            paragraph = index_title.get_paragraph()
352            if paragraph is None:
353                paragraph = Paragraph()
354                index_title.append(paragraph)
355            if text_style:
356                paragraph.style = text_style  # type: ignore
357            paragraph.text = title
def fill( self, document: Document | None = None, use_default_styles: bool = True) -> None:
377    def fill(  # noqa: C901
378        self,
379        document: Document | None = None,
380        use_default_styles: bool = True,
381    ) -> None:
382        """Fill the TOC with the titles found in the document. A TOC is not
383        contextual so it will catch all titles before and after its insertion.
384        If the TOC is not attached to a document, attach it beforehand or
385        provide one as argument.
386
387        For having a pretty TOC, let use_default_styles by default.
388
389        Arguments:
390
391            document -- Document
392
393            use_default_styles -- bool
394        """
395        # Find the body
396        if document is not None:
397            body: Element | None = document.body
398        else:
399            body = self.document_body
400        if body is None:
401            raise ValueError("The TOC must be related to a document somehow")
402
403        # Save the title
404        index_body = self.body
405        title = index_body.get_element("text:index-title")  # type: ignore
406
407        # Clean the old index-body
408        self.body = None
409        index_body = self.body
410
411        # Restore the title
412        if title and str(title):
413            index_body.insert(title, position=0)  # type: ignore
414
415        # Insert default TOC style
416        if use_default_styles:
417            automatic_styles = body.get_element("//office:automatic-styles")
418            if isinstance(automatic_styles, Element):
419                for level in range(1, 11):
420                    if (
421                        automatic_styles.get_style(
422                            "paragraph", _toc_entry_style_name(level)
423                        )
424                        is None
425                    ):
426                        level_style = default_toc_level_style(level)
427                        automatic_styles.append(level_style)
428
429        # Auto-fill the index
430        outline_level = self.outline_level or 10
431        level_indexes: dict[int, int] = {}
432        for header in body.get_headers():
433            level = header.get_attribute_integer("text:outline-level") or 0
434            if level is None or level > outline_level:
435                continue
436            number_str = self._header_numbering(level_indexes, level)
437            # Make the title with "1.2.3. Title" format
438            paragraph = Paragraph(f"{number_str} {header}")
439            if use_default_styles:
440                paragraph.style = _toc_entry_style_name(level)
441            index_body.append(paragraph)  # type: ignore

Fill the TOC with the titles found in the document. A TOC is not contextual so it will catch all titles before and after its insertion. If the TOC is not attached to a document, attach it beforehand or provide one as argument.

For having a pretty TOC, let use_default_styles by default.

Arguments:

document -- Document

use_default_styles -- bool
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
xml_id: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
protected: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
protection_key: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
protection_key_digest_algorithm: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Tab(odfdo.Element):
194class Tab(Element):
195    """This element represents the [UNICODE] tab character (HORIZONTAL
196    TABULATION, U+0009).
197
198    The position attribute contains the number of the tab-stop to which
199    a tab character refers. The position 0 marks the start margin of a
200    paragraph. Note: The position attribute is only a hint to help non-layout
201    oriented consumers to determine the tab/tab-stop association. Layout
202    oriented consumers should determine the tab positions based on the style
203    information
204    """
205
206    _tag = "text:tab"
207    _properties: tuple[PropDef, ...] = (PropDef("position", "text:tab-ref"),)
208
209    def __init__(self, position: int | None = None, **kwargs: Any) -> None:
210        """
211        Arguments:
212
213            position -- int
214        """
215        super().__init__(**kwargs)
216        if self._do_init and position is not None and position >= 0:
217            self.position = str(position)

This element represents the [UNICODE] tab character (HORIZONTAL TABULATION, U+0009).

The position attribute contains the number of the tab-stop to which a tab character refers. The position 0 marks the start margin of a paragraph. Note: The position attribute is only a hint to help non-layout oriented consumers to determine the tab/tab-stop association. Layout oriented consumers should determine the tab positions based on the style information

Tab(position: int | None = None, **kwargs: Any)
209    def __init__(self, position: int | None = None, **kwargs: Any) -> None:
210        """
211        Arguments:
212
213            position -- int
214        """
215        super().__init__(**kwargs)
216        if self._do_init and position is not None and position >= 0:
217            self.position = str(position)

Arguments:

position -- int
position: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TabStopStyle(odfdo.Element):
 94class TabStopStyle(Element):
 95    """ODF "style:tab-stop"
 96    Base style for a TOC entryBase style for a TOC entry
 97    """
 98
 99    _tag = "style:tab-stop"
100    _properties = (
101        PropDef("style_char", "style:char"),
102        PropDef("leader_color", "style:leader-color"),
103        PropDef("leader_style", "style:leader-style"),
104        PropDef("leader_text", "style:leader-text"),
105        PropDef("leader_text_style", "style:leader-text-style"),
106        PropDef("leader_type", "style:leader-type"),
107        PropDef("leader_width", "style:leader-width"),
108        PropDef("style_position", "style:position"),
109        PropDef("style_type", "style:type"),
110    )
111
112    def __init__(  # noqa: C901
113        self,
114        style_char: str | None = None,
115        leader_color: str | None = None,
116        leader_style: str | None = None,
117        leader_text: str | None = None,
118        leader_text_style: str | None = None,
119        leader_type: str | None = None,
120        leader_width: str | None = None,
121        style_position: str | None = None,
122        style_type: str | None = None,
123        **kwargs: Any,
124    ):
125        super().__init__(**kwargs)
126        if self._do_init:
127            if style_char:
128                self.style_char = style_char
129            if leader_color:
130                self.leader_color = leader_color
131            if leader_style:
132                self.leader_style = leader_style
133            if leader_text:
134                self.leader_text = leader_text
135            if leader_text_style:
136                self.leader_text_style = leader_text_style
137            if leader_type:
138                self.leader_type = leader_type
139            if leader_width:
140                self.leader_width = leader_width
141            if style_position:
142                self.style_position = style_position
143            if style_type:
144                self.style_type = style_type

ODF "style:tab-stop" Base style for a TOC entryBase style for a TOC entry

TabStopStyle( style_char: str | None = None, leader_color: str | None = None, leader_style: str | None = None, leader_text: str | None = None, leader_text_style: str | None = None, leader_type: str | None = None, leader_width: str | None = None, style_position: str | None = None, style_type: str | None = None, **kwargs: Any)
112    def __init__(  # noqa: C901
113        self,
114        style_char: str | None = None,
115        leader_color: str | None = None,
116        leader_style: str | None = None,
117        leader_text: str | None = None,
118        leader_text_style: str | None = None,
119        leader_type: str | None = None,
120        leader_width: str | None = None,
121        style_position: str | None = None,
122        style_type: str | None = None,
123        **kwargs: Any,
124    ):
125        super().__init__(**kwargs)
126        if self._do_init:
127            if style_char:
128                self.style_char = style_char
129            if leader_color:
130                self.leader_color = leader_color
131            if leader_style:
132                self.leader_style = leader_style
133            if leader_text:
134                self.leader_text = leader_text
135            if leader_text_style:
136                self.leader_text_style = leader_text_style
137            if leader_type:
138                self.leader_type = leader_type
139            if leader_width:
140                self.leader_width = leader_width
141            if style_position:
142                self.style_position = style_position
143            if style_type:
144                self.style_type = style_type
style_char: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_color: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_text: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_text_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
leader_width: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style_position: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Table(odfdo.Element):
 287class Table(Element):
 288    """ODF table "table:table" """
 289
 290    _tag = "table:table"
 291    _caching = True
 292    _append = Element.append
 293
 294    def __init__(
 295        self,
 296        name: str | None = None,
 297        width: int | None = None,
 298        height: int | None = None,
 299        protected: bool = False,
 300        protection_key: str | None = None,
 301        display: bool = True,
 302        printable: bool = True,
 303        print_ranges: list[str] | None = None,
 304        style: str | None = None,
 305        **kwargs: Any,
 306    ) -> None:
 307        """Create a table element, optionally prefilled with "height" rows of
 308        "width" cells each.
 309
 310        If the table is to be protected, a protection key must be provided,
 311        i.e. a hash value of the password.
 312
 313        If the table must not be displayed, set "display" to False.
 314
 315        If the table must not be printed, set "printable" to False. The table
 316        will not be printed when it is not displayed, whatever the value of
 317        this argument.
 318
 319        Ranges of cells to print can be provided as a list of cell ranges,
 320        e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g.
 321        "E6:K12 P6:R12".
 322
 323        You can access and modify the XML tree manually, but you probably want
 324        to use the API to access and alter cells. It will save you from
 325        handling repetitions and the same number of cells for each row.
 326
 327        If you use both the table API and the XML API, you are on your own for
 328        ensuiring model integrity.
 329
 330        Arguments:
 331
 332            name -- str
 333
 334            width -- int
 335
 336            height -- int
 337
 338            protected -- bool
 339
 340            protection_key -- str
 341
 342            display -- bool
 343
 344            printable -- bool
 345
 346            print_ranges -- list
 347
 348            style -- str
 349        """
 350        super().__init__(**kwargs)
 351        self._indexes = {}
 352        self._indexes["_cmap"] = {}
 353        self._indexes["_tmap"] = {}
 354        if self._do_init:
 355            self.name = name
 356            if protected:
 357                self.protected = protected
 358                self.set_protection_key = protection_key
 359            if not display:
 360                self.displayed = display
 361            if not printable:
 362                self.printable = printable
 363            if print_ranges:
 364                self.print_ranges = print_ranges
 365            if style:
 366                self.style = style
 367            # Prefill the table
 368            if width is not None or height is not None:
 369                width = width or 1
 370                height = height or 1
 371                # Column groups for style information
 372                columns = Column(repeated=width)
 373                self._append(columns)
 374                for _i in range(height):
 375                    row = Row(width)
 376                    self._append(row)
 377        self._compute_table_cache()
 378
 379    def __str__(self) -> str:
 380        def write_content(csv_writer: object) -> None:
 381            for values in self.iter_values():
 382                line = []
 383                for value in values:
 384                    if value is None:
 385                        value = ""
 386                    if isinstance(value, str):
 387                        value = value.strip()
 388                    line.append(value)
 389                csv_writer.writerow(line)  # type: ignore
 390
 391        out = StringIO(newline=os.linesep)
 392        csv_writer = csv.writer(
 393            out,
 394            delimiter=" ",
 395            doublequote=False,
 396            escapechar="\\",
 397            lineterminator=os.linesep,
 398            quotechar='"',
 399            quoting=csv.QUOTE_NONNUMERIC,
 400        )
 401        write_content(csv_writer)
 402        return out.getvalue()
 403
 404    def _translate_y_from_any(self, y: str | int) -> int:
 405        # "3" (couting from 1) -> 2 (couting from 0)
 406        return translate_from_any(y, self.height, 1)
 407
 408    def _translate_table_coordinates_list(
 409        self,
 410        coord: tuple | list,
 411    ) -> tuple[int | None, ...]:
 412        height = self.height
 413        width = self.width
 414        # assuming we got int values
 415        if len(coord) == 1:
 416            # It is a row
 417            y = coord[0]
 418            if y and y < 0:
 419                y = increment(y, height)
 420            return (None, y, None, y)
 421        if len(coord) == 2:
 422            # It is a row range, not a cell, because context is table
 423            y = coord[0]
 424            if y and y < 0:
 425                y = increment(y, height)
 426            t = coord[1]
 427            if t and t < 0:
 428                t = increment(t, height)
 429            return (None, y, None, t)
 430        # should be 4 int
 431        x, y, z, t = coord
 432        if x and x < 0:
 433            x = increment(x, width)
 434        if y and y < 0:
 435            y = increment(y, height)
 436        if z and z < 0:
 437            z = increment(z, width)
 438        if t and t < 0:
 439            t = increment(t, height)
 440        return (x, y, z, t)
 441
 442    def _translate_table_coordinates_str(
 443        self,
 444        coord_str: str,
 445    ) -> tuple[int | None, ...]:
 446        height = self.height
 447        width = self.width
 448        coord = convert_coordinates(coord_str)
 449        if len(coord) == 2:
 450            x, y = coord
 451            if x and x < 0:
 452                x = increment(x, width)
 453            if y and y < 0:
 454                y = increment(y, height)
 455            # extent to an area :
 456            return (x, y, x, y)
 457        x, y, z, t = coord
 458        if x and x < 0:
 459            x = increment(x, width)
 460        if y and y < 0:
 461            y = increment(y, height)
 462        if z and z < 0:
 463            z = increment(z, width)
 464        if t and t < 0:
 465            t = increment(t, height)
 466        return (x, y, z, t)
 467
 468    def _translate_table_coordinates(
 469        self,
 470        coord: tuple | list | str,
 471    ) -> tuple[int | None, ...]:
 472        if isinstance(coord, str):
 473            return self._translate_table_coordinates_str(coord)
 474        return self._translate_table_coordinates_list(coord)
 475
 476    def _translate_column_coordinates_str(
 477        self,
 478        coord_str: str,
 479    ) -> tuple[int | None, ...]:
 480        width = self.width
 481        height = self.height
 482        coord = convert_coordinates(coord_str)
 483        if len(coord) == 2:
 484            x, y = coord
 485            if x and x < 0:
 486                x = increment(x, width)
 487            if y and y < 0:
 488                y = increment(y, height)
 489            # extent to an area :
 490            return (x, y, x, y)
 491        x, y, z, t = coord
 492        if x and x < 0:
 493            x = increment(x, width)
 494        if y and y < 0:
 495            y = increment(y, height)
 496        if z and z < 0:
 497            z = increment(z, width)
 498        if t and t < 0:
 499            t = increment(t, height)
 500        return (x, y, z, t)
 501
 502    def _translate_column_coordinates_list(
 503        self,
 504        coord: tuple | list,
 505    ) -> tuple[int | None, ...]:
 506        width = self.width
 507        height = self.height
 508        # assuming we got int values
 509        if len(coord) == 1:
 510            # It is a column
 511            x = coord[0]
 512            if x and x < 0:
 513                x = increment(x, width)
 514            return (x, None, x, None)
 515        if len(coord) == 2:
 516            # It is a column range, not a cell, because context is table
 517            x = coord[0]
 518            if x and x < 0:
 519                x = increment(x, width)
 520            z = coord[1]
 521            if z and z < 0:
 522                z = increment(z, width)
 523            return (x, None, z, None)
 524        # should be 4 int
 525        x, y, z, t = coord
 526        if x and x < 0:
 527            x = increment(x, width)
 528        if y and y < 0:
 529            y = increment(y, height)
 530        if z and z < 0:
 531            z = increment(z, width)
 532        if t and t < 0:
 533            t = increment(t, height)
 534        return (x, y, z, t)
 535
 536    def _translate_column_coordinates(
 537        self,
 538        coord: tuple | list | str,
 539    ) -> tuple[int | None, ...]:
 540        if isinstance(coord, str):
 541            return self._translate_column_coordinates_str(coord)
 542        return self._translate_column_coordinates_list(coord)
 543
 544    def _translate_cell_coordinates(
 545        self,
 546        coord: tuple | list | str,
 547    ) -> tuple[int | None, int | None]:
 548        # we want an x,y result
 549        coord = convert_coordinates(coord)
 550        if len(coord) == 2:
 551            x, y = coord
 552        # If we got an area, take the first cell
 553        elif len(coord) == 4:
 554            x, y, z, t = coord
 555        else:
 556            raise ValueError(str(coord))
 557        if x and x < 0:
 558            x = increment(x, self.width)
 559        if y and y < 0:
 560            y = increment(y, self.height)
 561        return (x, y)
 562
 563    def _compute_table_cache(self) -> None:
 564        idx_repeated_seq = self.elements_repeated_sequence(
 565            _xpath_row, "table:number-rows-repeated"
 566        )
 567        self._tmap = make_cache_map(idx_repeated_seq)
 568        idx_repeated_seq = self.elements_repeated_sequence(
 569            _xpath_column, "table:number-columns-repeated"
 570        )
 571        self._cmap = make_cache_map(idx_repeated_seq)
 572
 573    def _update_width(self, row: Row) -> None:
 574        """Synchronize the number of columns if the row is bigger.
 575
 576        Append, don't insert, not to disturb the current layout.
 577        """
 578        diff = row.width - self.width
 579        if diff > 0:
 580            self.append_column(Column(repeated=diff))
 581
 582    def _get_formatted_text_normal(self, context: dict | None) -> str:
 583        result = []
 584        for row in self.traverse():
 585            for cell in row.traverse():
 586                value = cell.get_value(try_get_text=False)
 587                # None ?
 588                if value is None:
 589                    # Try with get_formatted_text on the elements
 590                    value = []
 591                    for element in cell.children:
 592                        value.append(element.get_formatted_text(context))
 593                    value = "".join(value)
 594                else:
 595                    value = str(value)
 596                result.append(value)
 597                result.append("\n")
 598            result.append("\n")
 599        return "".join(result)
 600
 601    def _get_formatted_text_rst(self, context: dict) -> str:  # noqa: C901
 602        context["no_img_level"] += 1
 603        # Strip the table => We must clone
 604        table = self.clone
 605        table.rstrip(aggressive=True)  # type: ignore
 606
 607        # Fill the rows
 608        rows = []
 609        cols_nb = 0
 610        cols_size: dict[int, int] = {}
 611        for odf_row in table.traverse():  # type: ignore
 612            row = []
 613            for i, cell in enumerate(odf_row.traverse()):
 614                value = cell.get_value(try_get_text=False)
 615                # None ?
 616                if value is None:
 617                    # Try with get_formatted_text on the elements
 618                    value = []
 619                    for element in cell.children:
 620                        value.append(element.get_formatted_text(context))
 621                    value = "".join(value)
 622                else:
 623                    value = str(value)
 624                value = value.strip()
 625                # Strip the empty columns
 626                if value:
 627                    cols_nb = max(cols_nb, i + 1)
 628                # Compute the size of each columns (at least 2)
 629                cols_size[i] = max(cols_size.get(i, 2), len(value))
 630                # Append
 631                row.append(value)
 632            rows.append(row)
 633
 634        # Nothing ?
 635        if cols_nb == 0:
 636            return ""
 637
 638        # Prevent a crash with empty columns (by example with images)
 639        for col, size in cols_size.items():
 640            if size == 0:
 641                cols_size[col] = 1
 642
 643        # Update cols_size
 644        LINE_MAX = 100
 645        COL_MIN = 16
 646
 647        free_size = LINE_MAX - (cols_nb - 1) * 3 - 4
 648        real_size = sum([cols_size[i] for i in range(cols_nb)])
 649        if real_size > free_size:
 650            factor = float(free_size) / real_size
 651
 652            for i in range(cols_nb):
 653                old_size = cols_size[i]
 654
 655                # The cell is already small
 656                if old_size <= COL_MIN:
 657                    continue
 658
 659                new_size = int(factor * old_size)
 660
 661                if new_size < COL_MIN:
 662                    new_size = COL_MIN
 663                cols_size[i] = new_size
 664
 665        # Convert !
 666        result: list[str] = [""]
 667        # Construct the first/last line
 668        line: list[str] = []
 669        for i in range(cols_nb):
 670            line.append("=" * cols_size[i])
 671            line.append(" ")
 672        line_str = "".join(line)
 673
 674        # Add the lines
 675        result.append(line_str)
 676        for row in rows:
 677            # Wrap the row
 678            wrapped_row = []
 679            for i, value in enumerate(row[:cols_nb]):
 680                wrapped_value = []
 681                for part in value.split("\n"):
 682                    # Hack to handle correctly the lists or the directives
 683                    subsequent_indent = ""
 684                    part_lstripped = part.lstrip()
 685                    if part_lstripped.startswith("-") or part_lstripped.startswith(
 686                        ".."
 687                    ):
 688                        subsequent_indent = " " * (len(part) - len(part.lstrip()) + 2)
 689                    wrapped_part = wrap(
 690                        part, width=cols_size[i], subsequent_indent=subsequent_indent
 691                    )
 692                    if wrapped_part:
 693                        wrapped_value.extend(wrapped_part)
 694                    else:
 695                        wrapped_value.append("")
 696                wrapped_row.append(wrapped_value)
 697
 698            # Append!
 699            for j in range(max([1] + [len(values) for values in wrapped_row])):
 700                txt_row: list[str] = []
 701                for i in range(cols_nb):
 702                    values = wrapped_row[i] if i < len(wrapped_row) else []
 703
 704                    # An empty cell ?
 705                    if len(values) - 1 < j or not values[j]:
 706                        if i == 0 and j == 0:
 707                            txt_row.append("..")
 708                            txt_row.append(" " * (cols_size[i] - 1))
 709                        else:
 710                            txt_row.append(" " * (cols_size[i] + 1))
 711                        continue
 712
 713                    # Not empty
 714                    value = values[j]
 715                    txt_row.append(value)
 716                    txt_row.append(" " * (cols_size[i] - len(value) + 1))
 717                result.append("".join(txt_row))
 718
 719        result.append(line_str)
 720        result.append("")
 721        result.append("")
 722        result_str = "\n".join(result)
 723
 724        context["no_img_level"] -= 1
 725        return result_str
 726
 727    def _translate_x_from_any(self, x: str | int) -> int:
 728        return translate_from_any(x, self.width, 0)
 729
 730    #
 731    # Public API
 732    #
 733
 734    def append(self, something: Element | str) -> None:
 735        """Dispatch .append() call to append_row() or append_column()."""
 736        if isinstance(something, Row):
 737            self.append_row(something)
 738        elif isinstance(something, Column):
 739            self.append_column(something)
 740        else:
 741            # probably still an error
 742            self._append(something)
 743
 744    @property
 745    def height(self) -> int:
 746        """Get the current height of the table.
 747
 748        Return: int
 749        """
 750        try:
 751            height = self._tmap[-1] + 1
 752        except Exception:
 753            height = 0
 754        return height
 755
 756    @property
 757    def width(self) -> int:
 758        """Get the current width of the table, measured on columns.
 759
 760        Rows may have different widths, use the Table API to ensure width
 761        consistency.
 762
 763        Return: int
 764        """
 765        # Columns are our reference for user expected width
 766
 767        try:
 768            width = self._cmap[-1] + 1
 769        except Exception:
 770            width = 0
 771
 772        # columns = self._get_columns()
 773        # repeated = self.xpath(
 774        #        'table:table-column/@table:number-columns-repeated')
 775        # unrepeated = len(columns) - len(repeated)
 776        # ws = sum(int(r) for r in repeated) + unrepeated
 777        # if w != ws:
 778        #    print "WARNING   ws", ws, "w", w
 779
 780        return width
 781
 782    @property
 783    def size(self) -> tuple[int, int]:
 784        """Shortcut to get the current width and height of the table.
 785
 786        Return: (int, int)
 787        """
 788        return self.width, self.height
 789
 790    @property
 791    def name(self) -> str | None:
 792        """Get / set the name of the table."""
 793        return self.get_attribute_string("table:name")
 794
 795    @name.setter
 796    def name(self, name: str) -> None:
 797        name = _table_name_check(name)
 798        # first, update named ranges
 799        # fixme : delete name ranges when deleting table, too.
 800        for named_range in self.get_named_ranges(table_name=self.name):
 801            named_range.set_table_name(name)
 802        self.set_attribute("table:name", name)
 803
 804    @property
 805    def protected(self) -> bool:
 806        return bool(self.get_attribute("table:protected"))
 807
 808    @protected.setter
 809    def protected(self, protect: bool) -> None:
 810        self.set_attribute("table:protected", protect)
 811
 812    @property
 813    def protection_key(self) -> str | None:
 814        return self.get_attribute_string("table:protection-key")
 815
 816    @protection_key.setter
 817    def protection_key(self, key: str) -> None:
 818        self.set_attribute("table:protection-key", key)
 819
 820    @property
 821    def displayed(self) -> bool:
 822        return bool(self.get_attribute("table:display"))
 823
 824    @displayed.setter
 825    def displayed(self, display: bool) -> None:
 826        self.set_attribute("table:display", display)
 827
 828    @property
 829    def printable(self) -> bool:
 830        printable = self.get_attribute("table:print")
 831        # Default value
 832        if printable is None:
 833            return True
 834        return bool(printable)
 835
 836    @printable.setter
 837    def printable(self, printable: bool) -> None:
 838        self.set_attribute("table:print", printable)
 839
 840    @property
 841    def print_ranges(self) -> list[str]:
 842        ranges = self.get_attribute_string("table:print-ranges")
 843        if isinstance(ranges, str):
 844            return ranges.split()
 845        return []
 846
 847    @print_ranges.setter
 848    def print_ranges(self, ranges: list[str] | None) -> None:
 849        if isinstance(ranges, (list, tuple)):
 850            self.set_attribute("table:print-ranges", " ".join(ranges))
 851        else:
 852            self.set_attribute("table:print-ranges", ranges)
 853
 854    @property
 855    def style(self) -> str | None:
 856        """Get / set the style of the table
 857
 858        Return: str
 859        """
 860        return self.get_attribute_string("table:style-name")
 861
 862    @style.setter
 863    def style(self, style: str | Element) -> None:
 864        self.set_style_attribute("table:style-name", style)
 865
 866    def get_formatted_text(self, context: dict | None = None) -> str:
 867        if context and context["rst_mode"]:
 868            return self._get_formatted_text_rst(context)
 869        return self._get_formatted_text_normal(context)
 870
 871    def get_values(
 872        self,
 873        coord: tuple | list | str | None = None,
 874        cell_type: str | None = None,
 875        complete: bool = True,
 876        get_type: bool = False,
 877        flat: bool = False,
 878    ) -> list:
 879        """Get a matrix of values of the table.
 880
 881        Filter by coordinates will parse the area defined by the coordinates.
 882
 883        If 'cell_type' is used and 'complete' is True (default), missing values
 884        are replaced by None.
 885        Filter by ' cell_type = "all" ' will retrieve cells of any
 886        type, aka non empty cells.
 887
 888        If 'cell_type' is None, complete is always True : with no cell type
 889        queried, get_values() returns None for each empty cell, the length
 890        each lists is equal to the width of the table.
 891
 892        If get_type is True, returns tuples (value, ODF type of value), or
 893        (None, None) for empty cells with complete True.
 894
 895        If flat is True, the methods return a single list of all the values.
 896        By default, flat is False.
 897
 898        Arguments:
 899
 900            coord -- str or tuple of int : coordinates of area
 901
 902            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
 903                         'currency', 'percentage' or 'all'
 904
 905            complete -- boolean
 906
 907            get_type -- boolean
 908
 909        Return: list of lists of Python types
 910        """
 911        if coord:
 912            x, y, z, t = self._translate_table_coordinates(coord)
 913        else:
 914            x = y = z = t = None
 915        data = []
 916        for row in self.traverse(start=y, end=t):
 917            if z is None:
 918                width = self.width
 919            else:
 920                width = min(z + 1, self.width)
 921            if x is not None:
 922                width -= x
 923            values = row.get_values(
 924                (x, z),
 925                cell_type=cell_type,
 926                complete=complete,
 927                get_type=get_type,
 928            )
 929            # complete row to match request width
 930            if complete:
 931                if get_type:
 932                    values.extend([(None, None)] * (width - len(values)))
 933                else:
 934                    values.extend([None] * (width - len(values)))
 935            if flat:
 936                data.extend(values)
 937            else:
 938                data.append(values)
 939        return data
 940
 941    def iter_values(
 942        self,
 943        coord: tuple | list | str | None = None,
 944        cell_type: str | None = None,
 945        complete: bool = True,
 946        get_type: bool = False,
 947    ) -> Iterator[list]:
 948        """Iterate through lines of Python values of the table.
 949
 950        Filter by coordinates will parse the area defined by the coordinates.
 951
 952        cell_type, complete, grt_type : see get_values()
 953
 954
 955
 956        Arguments:
 957
 958            coord -- str or tuple of int : coordinates of area
 959
 960            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
 961                         'currency', 'percentage' or 'all'
 962
 963            complete -- boolean
 964
 965            get_type -- boolean
 966
 967        Return: iterator of lists
 968        """
 969        if coord:
 970            x, y, z, t = self._translate_table_coordinates(coord)
 971        else:
 972            x = y = z = t = None
 973        for row in self.traverse(start=y, end=t):
 974            if z is None:
 975                width = self.width
 976            else:
 977                width = min(z + 1, self.width)
 978            if x is not None:
 979                width -= x
 980            values = row.get_values(
 981                (x, z),
 982                cell_type=cell_type,
 983                complete=complete,
 984                get_type=get_type,
 985            )
 986            # complete row to match column width
 987            if complete:
 988                if get_type:
 989                    values.extend([(None, None)] * (width - len(values)))
 990                else:
 991                    values.extend([None] * (width - len(values)))
 992            yield values
 993
 994    def set_values(
 995        self,
 996        values: list,
 997        coord: tuple | list | str | None = None,
 998        style: str | None = None,
 999        cell_type: str | None = None,
1000        currency: str | None = None,
1001    ) -> None:
1002        """Set the value of cells in the table, from the 'coord' position
1003        with values.
1004
1005        'coord' is the coordinate of the upper left cell to be modified by
1006        values. If 'coord' is None, default to the position (0,0) ("A1").
1007        If 'coord' is an area (e.g. "A2:B5"), the upper left position of this
1008        area is used as coordinate.
1009
1010        The table is *not* cleared before the operation, to reset the table
1011        before setting values, use table.clear().
1012
1013        A list of lists is expected, with as many lists as rows, and as many
1014        items in each sublist as cells to be setted. None values in the list
1015        will create empty cells with no cell type (but eventually a style).
1016
1017        Arguments:
1018
1019            coord -- tuple or str
1020
1021            values -- list of lists of python types
1022
1023            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1024                         'string' or 'time'
1025
1026            currency -- three-letter str
1027
1028            style -- str
1029        """
1030        if coord:
1031            x, y = self._translate_cell_coordinates(coord)
1032        else:
1033            x = y = 0
1034        if y is None:
1035            y = 0
1036        if x is None:
1037            x = 0
1038        y -= 1
1039        for row_values in values:
1040            y += 1
1041            if not row_values:
1042                continue
1043            row = self.get_row(y, clone=True)
1044            repeated = row.repeated or 1
1045            if repeated >= 2:
1046                row.repeated = None
1047            row.set_values(
1048                row_values,
1049                start=x,
1050                cell_type=cell_type,
1051                currency=currency,
1052                style=style,
1053            )
1054            self.set_row(y, row, clone=False)
1055            self._update_width(row)
1056
1057    def rstrip(self, aggressive: bool = False) -> None:
1058        """Remove *in-place* empty rows below and empty cells at the right of
1059        the table. Cells are empty if they contain no value or it evaluates
1060        to False, and no style.
1061
1062        If aggressive is True, empty cells with style are removed too.
1063
1064        Argument:
1065
1066            aggressive -- bool
1067        """
1068        # Step 1: remove empty rows below the table
1069        for row in reversed(self._get_rows()):
1070            if row.is_empty(aggressive=aggressive):
1071                row.parent.delete(row)  # type: ignore
1072            else:
1073                break
1074        # Step 2: rstrip remaining rows
1075        max_width = 0
1076        for row in self._get_rows():
1077            row.rstrip(aggressive=aggressive)
1078            # keep count of the biggest row
1079            max_width = max(max_width, row.width)
1080        # raz cache of rows
1081        self._indexes["_tmap"] = {}
1082        # Step 3: trim columns to match max_width
1083        columns = self._get_columns()
1084        repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated")
1085        if not isinstance(repeated_cols, list):
1086            raise TypeError
1087        unrepeated = len(columns) - len(repeated_cols)
1088        column_width = sum(int(r) for r in repeated_cols) + unrepeated  # type: ignore
1089        diff = column_width - max_width
1090        if diff > 0:
1091            for column in reversed(columns):
1092                repeated = column.repeated or 1
1093                repeated = repeated - diff
1094                if repeated > 0:
1095                    column.repeated = repeated
1096                    break
1097                else:
1098                    column.parent.delete(column)
1099                    diff = -repeated
1100                    if diff == 0:
1101                        break
1102        # raz cache of columns
1103        self._indexes["_cmap"] = {}
1104        self._compute_table_cache()
1105
1106    def optimize_width(self) -> None:
1107        """Remove *in-place* empty rows below and empty cells at the right of
1108        the table. Keep repeated styles of empty cells but minimize row width.
1109        """
1110        self._optimize_width_trim_rows()
1111        width = self._optimize_width_length()
1112        self._optimize_width_rstrip_rows(width)
1113        self._optimize_width_adapt_columns(width)
1114
1115    def _optimize_width_trim_rows(self) -> None:
1116        count = -1  # to keep one empty row
1117        for row in reversed(self._get_rows()):
1118            if row.is_empty(aggressive=False):
1119                count += 1
1120            else:
1121                break
1122        if count > 0:
1123            for row in reversed(self._get_rows()):
1124                row.parent.delete(row)  # type: ignore
1125                count -= 1
1126                if count <= 0:
1127                    break
1128        try:
1129            last_row = self._get_rows()[-1]
1130            last_row._set_repeated(None)
1131        except IndexError:
1132            pass
1133        # raz cache of rows
1134        self._indexes["_tmap"] = {}
1135
1136    def _optimize_width_length(self) -> int:
1137        return max(row.minimized_width() for row in self._get_rows())
1138
1139    def _optimize_width_rstrip_rows(self, width: int) -> None:
1140        for row in self._get_rows():
1141            row.force_width(width)
1142
1143    def _optimize_width_adapt_columns(self, width: int) -> None:
1144        # trim columns to match minimal_width
1145        columns = self._get_columns()
1146        repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated")
1147        if not isinstance(repeated_cols, list):
1148            raise TypeError
1149        unrepeated = len(columns) - len(repeated_cols)
1150        column_width = sum(int(r) for r in repeated_cols) + unrepeated  # type: ignore
1151        diff = column_width - width
1152        if diff > 0:
1153            for column in reversed(columns):
1154                repeated = column.repeated or 1
1155                repeated = repeated - diff
1156                if repeated > 0:
1157                    column.repeated = repeated
1158                    break
1159                else:
1160                    column.parent.delete(column)
1161                    diff = -repeated
1162                    if diff == 0:
1163                        break
1164        # raz cache of columns
1165        self._indexes["_cmap"] = {}
1166        self._compute_table_cache()
1167
1168    def transpose(self, coord: tuple | list | str | None = None) -> None:  # noqa: C901
1169        """Swap *in-place* rows and columns of the table.
1170
1171        If 'coord' is not None, apply transpose only to the area defined by the
1172        coordinates. Beware, if area is not square, some cells mays be over
1173        written during the process.
1174
1175        Arguments:
1176
1177            coord -- str or tuple of int : coordinates of area
1178
1179            start -- int or str
1180        """
1181        data = []
1182        if coord is None:
1183            for row in self.traverse():
1184                data.append(list(row.traverse()))
1185            transposed_data = zip_longest(*data)
1186            self.clear()
1187            # new_rows = []
1188            for row_cells in transposed_data:
1189                if not isiterable(row_cells):
1190                    row_cells = (row_cells,)
1191                row = Row()
1192                row.extend_cells(row_cells)
1193                self.append_row(row, clone=False)
1194            self._compute_table_cache()
1195        else:
1196            x, y, z, t = self._translate_table_coordinates(coord)
1197            if x is None:
1198                x = 0
1199            else:
1200                x = min(x, self.width - 1)
1201            if z is None:
1202                z = self.width - 1
1203            else:
1204                z = min(z, self.width - 1)
1205            if y is None:
1206                y = 0
1207            else:
1208                y = min(y, self.height - 1)
1209            if t is None:
1210                t = self.height - 1
1211            else:
1212                t = min(t, self.height - 1)
1213            for row in self.traverse(start=y, end=t):
1214                data.append(list(row.traverse(start=x, end=z)))
1215            transposed_data = zip_longest(*data)
1216            # clear locally
1217            w = z - x + 1
1218            h = t - y + 1
1219            if w != h:
1220                nones = [[None] * w for i in range(h)]
1221                self.set_values(nones, coord=(x, y, z, t))
1222            # put transposed
1223            filtered_data: list[tuple[Cell]] = []
1224            for row_cells in transposed_data:
1225                if isinstance(row_cells, (list, tuple)):
1226                    filtered_data.append(row_cells)
1227                else:
1228                    filtered_data.append((row_cells,))
1229            self.set_cells(filtered_data, (x, y, x + h - 1, y + w - 1))
1230            self._compute_table_cache()
1231
1232    def is_empty(self, aggressive: bool = False) -> bool:
1233        """Return whether every cell in the table has no value or the value
1234        evaluates to False (empty string), and no style.
1235
1236        If aggressive is True, empty cells with style are considered empty.
1237
1238        Arguments:
1239
1240            aggressive -- bool
1241        """
1242        return all(row.is_empty(aggressive=aggressive) for row in self._get_rows())
1243
1244    #
1245    # Rows
1246    #
1247
1248    def _get_rows(self) -> list[Row]:
1249        return self.get_elements(_xpath_row)  # type: ignore
1250
1251    def traverse(  # noqa: C901
1252        self,
1253        start: int | None = None,
1254        end: int | None = None,
1255    ) -> Iterator[Row]:
1256        """Yield as many row elements as expected rows in the table, i.e.
1257        expand repetitions by returning the same row as many times as
1258        necessary.
1259
1260            Arguments:
1261
1262                start -- int
1263
1264                end -- int
1265
1266        Copies are returned, use set_row() to push them back.
1267        """
1268        idx = -1
1269        before = -1
1270        y = 0
1271        if start is None and end is None:
1272            for juska in self._tmap:
1273                idx += 1
1274                if idx in self._indexes["_tmap"]:
1275                    row = self._indexes["_tmap"][idx]
1276                else:
1277                    row = self._get_element_idx2(_xpath_row_idx, idx)
1278                    self._indexes["_tmap"][idx] = row
1279                repeated = juska - before
1280                before = juska
1281                for _i in range(repeated or 1):
1282                    # Return a copy without the now obsolete repetition
1283                    row = row.clone
1284                    row.y = y
1285                    y += 1
1286                    if repeated > 1:
1287                        row.repeated = None
1288                    yield row
1289        else:
1290            if start is None:
1291                start = 0
1292            start = max(0, start)
1293            if end is None:
1294                try:
1295                    end = self._tmap[-1]
1296                except Exception:
1297                    end = -1
1298            start_map = find_odf_idx(self._tmap, start)
1299            if start_map is None:
1300                return
1301            if start_map > 0:
1302                before = self._tmap[start_map - 1]
1303            idx = start_map - 1
1304            before = start - 1
1305            y = start
1306            for juska in self._tmap[start_map:]:
1307                idx += 1
1308                if idx in self._indexes["_tmap"]:
1309                    row = self._indexes["_tmap"][idx]
1310                else:
1311                    row = self._get_element_idx2(_xpath_row_idx, idx)
1312                    self._indexes["_tmap"][idx] = row
1313                repeated = juska - before
1314                before = juska
1315                for _i in range(repeated or 1):
1316                    if y <= end:
1317                        row = row.clone
1318                        row.y = y
1319                        y += 1
1320                        if repeated > 1 or (y == start and start > 0):
1321                            row.repeated = None
1322                        yield row
1323
1324    def get_rows(
1325        self,
1326        coord: tuple | list | str | None = None,
1327        style: str | None = None,
1328        content: str | None = None,
1329    ) -> list[Row]:
1330        """Get the list of rows matching the criteria.
1331
1332        Filter by coordinates will parse the area defined by the coordinates.
1333
1334        Arguments:
1335
1336            coord -- str or tuple of int : coordinates of rows
1337
1338            content -- str regex
1339
1340            style -- str
1341
1342        Return: list of rows
1343        """
1344        if coord:
1345            _x, y, _z, t = self._translate_table_coordinates(coord)
1346        else:
1347            y = t = None
1348        # fixme : not clones ?
1349        if not content and not style:
1350            return list(self.traverse(start=y, end=t))
1351        rows = []
1352        for row in self.traverse(start=y, end=t):
1353            if content and not row.match(content):
1354                continue
1355            if style and style != row.style:
1356                continue
1357            rows.append(row)
1358        return rows
1359
1360    def _get_row2(self, y: int, clone: bool = True, create: bool = True) -> Row:
1361        if y >= self.height:
1362            if create:
1363                return Row()
1364            raise ValueError("Row not found")
1365        row = self._get_row2_base(y)
1366        if row is None:
1367            raise ValueError("Row not found")
1368        if clone:
1369            return row.clone
1370        return row
1371
1372    def _get_row2_base(self, y: int) -> Row | None:
1373        idx = find_odf_idx(self._tmap, y)
1374        if idx is not None:
1375            if idx in self._indexes["_tmap"]:
1376                row = self._indexes["_tmap"][idx]
1377            else:
1378                row = self._get_element_idx2(_xpath_row_idx, idx)
1379                self._indexes["_tmap"][idx] = row
1380            return row
1381        return None
1382
1383    def get_row(self, y: int | str, clone: bool = True, create: bool = True) -> Row:
1384        """Get the row at the given "y" position.
1385
1386        Position start at 0. So cell A4 is on row 3.
1387
1388        A copy is returned, use set_cell() to push it back.
1389
1390        Arguments:
1391
1392            y -- int or str
1393
1394        Return: Row
1395        """
1396        # fixme : keep repeat ? maybe an option to functions : "raw=False"
1397        y = self._translate_y_from_any(y)
1398        row = self._get_row2(y, clone=clone, create=create)
1399        if row is None:
1400            raise ValueError("Row not found")
1401        row.y = y
1402        return row
1403
1404    def set_row(self, y: int | str, row: Row | None = None, clone: bool = True) -> Row:
1405        """Replace the row at the given position with the new one. Repetions of
1406        the old row will be adjusted.
1407
1408        If row is None, a new empty row is created.
1409
1410        Position start at 0. So cell A4 is on row 3.
1411
1412        Arguments:
1413
1414            y -- int or str
1415
1416            row -- Row
1417
1418        returns the row, with updated row.y
1419        """
1420        if row is None:
1421            row = Row()
1422            repeated = 1
1423            clone = False
1424        else:
1425            repeated = row.repeated or 1
1426        y = self._translate_y_from_any(y)
1427        row.y = y
1428        # Outside the defined table ?
1429        diff = y - self.height
1430        if diff == 0:
1431            row_back = self.append_row(row, _repeated=repeated, clone=clone)
1432        elif diff > 0:
1433            self.append_row(Row(repeated=diff), _repeated=diff, clone=clone)
1434            row_back = self.append_row(row, _repeated=repeated, clone=clone)
1435        else:
1436            # Inside the defined table
1437            row_back = set_item_in_vault(  # type: ignore
1438                y, row, self, _xpath_row_idx, "_tmap", clone=clone
1439            )
1440        # print self.serialize(True)
1441        # Update width if necessary
1442        self._update_width(row_back)
1443        return row_back
1444
1445    def insert_row(
1446        self, y: str | int, row: Row | None = None, clone: bool = True
1447    ) -> Row:
1448        """Insert the row before the given "y" position. If no row is given,
1449        an empty one is created.
1450
1451        Position start at 0. So cell A4 is on row 3.
1452
1453        If row is None, a new empty row is created.
1454
1455        Arguments:
1456
1457            y -- int or str
1458
1459            row -- Row
1460
1461        returns the row, with updated row.y
1462        """
1463        if row is None:
1464            row = Row()
1465            clone = False
1466        y = self._translate_y_from_any(y)
1467        diff = y - self.height
1468        if diff < 0:
1469            row_back = insert_item_in_vault(y, row, self, _xpath_row_idx, "_tmap")
1470        elif diff == 0:
1471            row_back = self.append_row(row, clone=clone)
1472        else:
1473            self.append_row(Row(repeated=diff), _repeated=diff, clone=False)
1474            row_back = self.append_row(row, clone=clone)
1475        row_back.y = y  # type: ignore
1476        # Update width if necessary
1477        self._update_width(row_back)  # type: ignore
1478        return row_back  # type: ignore
1479
1480    def extend_rows(self, rows: list[Row] | None = None) -> None:
1481        """Append a list of rows at the end of the table.
1482
1483        Arguments:
1484
1485            rows -- list of Row
1486        """
1487        if rows is None:
1488            rows = []
1489        self.extend(rows)
1490        self._compute_table_cache()
1491        # Update width if necessary
1492        width = self.width
1493        for row in self.traverse():
1494            if row.width > width:
1495                width = row.width
1496        diff = width - self.width
1497        if diff > 0:
1498            self.append_column(Column(repeated=diff))
1499
1500    def append_row(
1501        self,
1502        row: Row | None = None,
1503        clone: bool = True,
1504        _repeated: int | None = None,
1505    ) -> Row:
1506        """Append the row at the end of the table. If no row is given, an
1507        empty one is created.
1508
1509        Position start at 0. So cell A4 is on row 3.
1510
1511        Note the columns are automatically created when the first row is
1512        inserted in an empty table. So better insert a filled row.
1513
1514        Arguments:
1515
1516            row -- Row
1517
1518            _repeated -- (optional), repeated value of the row
1519
1520        returns the row, with updated row.y
1521        """
1522        if row is None:
1523            row = Row()
1524            _repeated = 1
1525        elif clone:
1526            row = row.clone
1527        # Appending a repeated row accepted
1528        # Do not insert next to the last row because it could be in a group
1529        self._append(row)
1530        if _repeated is None:
1531            _repeated = row.repeated or 1
1532        self._tmap = insert_map_once(self._tmap, len(self._tmap), _repeated)
1533        row.y = self.height - 1
1534        # Initialize columns
1535        if not self._get_columns():
1536            repeated = row.width
1537            self.insert(Column(repeated=repeated), position=0)
1538            self._compute_table_cache()
1539        # Update width if necessary
1540        self._update_width(row)
1541        return row
1542
1543    def delete_row(self, y: int | str) -> None:
1544        """Delete the row at the given "y" position.
1545
1546        Position start at 0. So cell A4 is on row 3.
1547
1548        Arguments:
1549
1550            y -- int or str
1551        """
1552        y = self._translate_y_from_any(y)
1553        # Outside the defined table
1554        if y >= self.height:
1555            return
1556        # Inside the defined table
1557        delete_item_in_vault(y, self, _xpath_row_idx, "_tmap")
1558
1559    def get_row_values(
1560        self,
1561        y: int | str,
1562        cell_type: str | None = None,
1563        complete: bool = True,
1564        get_type: bool = False,
1565    ) -> list:
1566        """Shortcut to get the list of Python values for the cells of the row
1567        at the given "y" position.
1568
1569        Position start at 0. So cell A4 is on row 3.
1570
1571        Filter by cell_type, with cell_type 'all' will retrieve cells of any
1572        type, aka non empty cells.
1573        If cell_type and complete is True, replace missing values by None.
1574
1575        If get_type is True, returns a tuple (value, ODF type of value)
1576
1577        Arguments:
1578
1579            y -- int, str
1580
1581            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
1582                         'currency', 'percentage' or 'all'
1583
1584            complete -- boolean
1585
1586            get_type -- boolean
1587
1588        Return: list of lists of Python types
1589        """
1590        values = self.get_row(y, clone=False).get_values(
1591            cell_type=cell_type, complete=complete, get_type=get_type
1592        )
1593        # complete row to match column width
1594        if complete:
1595            if get_type:
1596                values.extend([(None, None)] * (self.width - len(values)))
1597            else:
1598                values.extend([None] * (self.width - len(values)))
1599        return values
1600
1601    def set_row_values(
1602        self,
1603        y: int | str,
1604        values: list,
1605        cell_type: str | None = None,
1606        currency: str | None = None,
1607        style: str | None = None,
1608    ) -> Row:
1609        """Shortcut to set the values of *all* cells of the row at the given
1610        "y" position.
1611
1612        Position start at 0. So cell A4 is on row 3.
1613
1614        Arguments:
1615
1616            y -- int or str
1617
1618            values -- list of Python types
1619
1620            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1621                         'string' or 'time'
1622
1623            currency -- three-letter str
1624
1625            style -- str
1626
1627        returns the row, with updated row.y
1628        """
1629        row = Row()  # needed if clones rows
1630        row.set_values(values, style=style, cell_type=cell_type, currency=currency)
1631        return self.set_row(y, row)  # needed if clones rows
1632
1633    def set_row_cells(self, y: int | str, cells: list | None = None) -> Row:
1634        """Shortcut to set *all* the cells of the row at the given
1635        "y" position.
1636
1637        Position start at 0. So cell A4 is on row 3.
1638
1639        Arguments:
1640
1641            y -- int or str
1642
1643            cells -- list of Python types
1644
1645            style -- str
1646
1647        returns the row, with updated row.y
1648        """
1649        if cells is None:
1650            cells = []
1651        row = Row()  # needed if clones rows
1652        row.extend_cells(cells)
1653        return self.set_row(y, row)  # needed if clones rows
1654
1655    def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool:
1656        """Return wether every cell in the row at the given "y" position has
1657        no value or the value evaluates to False (empty string), and no style.
1658
1659        Position start at 0. So cell A4 is on row 3.
1660
1661        If aggressive is True, empty cells with style are considered empty.
1662
1663        Arguments:
1664
1665            y -- int or str
1666
1667            aggressive -- bool
1668        """
1669        return self.get_row(y, clone=False).is_empty(aggressive=aggressive)
1670
1671    #
1672    # Cells
1673    #
1674
1675    def get_cells(
1676        self,
1677        coord: tuple | list | str | None = None,
1678        cell_type: str | None = None,
1679        style: str | None = None,
1680        content: str | None = None,
1681        flat: bool = False,
1682    ) -> list:
1683        """Get the cells matching the criteria. If 'coord' is None,
1684        parse the whole table, else parse the area defined by 'coord'.
1685
1686        Filter by  cell_type = "all"  will retrieve cells of any
1687        type, aka non empty cells.
1688
1689        If flat is True (default is False), the method return a single list
1690        of all the values, else a list of lists of cells.
1691
1692        if cell_type, style and content are None, get_cells() will return
1693        the exact number of cells of the area, including empty cells.
1694
1695        Arguments:
1696
1697            coordinates -- str or tuple of int : coordinates of area
1698
1699            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
1700                         'currency', 'percentage' or 'all'
1701
1702            content -- str regex
1703
1704            style -- str
1705
1706            flat -- boolean
1707
1708        Return: list of tuples
1709        """
1710        if coord:
1711            x, y, z, t = self._translate_table_coordinates(coord)
1712        else:
1713            x = y = z = t = None
1714        if flat:
1715            cells: list[Cell] = []
1716            for row in self.traverse(start=y, end=t):
1717                row_cells = row.get_cells(
1718                    coord=(x, z),
1719                    cell_type=cell_type,
1720                    style=style,
1721                    content=content,
1722                )
1723                cells.extend(row_cells)
1724            return cells
1725        else:
1726            lcells: list[list[Cell]] = []
1727            for row in self.traverse(start=y, end=t):
1728                row_cells = row.get_cells(
1729                    coord=(x, z),
1730                    cell_type=cell_type,
1731                    style=style,
1732                    content=content,
1733                )
1734                lcells.append(row_cells)
1735            return lcells
1736
1737    def get_cell(
1738        self,
1739        coord: tuple | list | str,
1740        clone: bool = True,
1741        keep_repeated: bool = True,
1742    ) -> Cell:
1743        """Get the cell at the given coordinates.
1744
1745        They are either a 2-uplet of (x, y) starting from 0, or a
1746        human-readable position like "C4".
1747
1748        A copy is returned, use ``set_cell`` to push it back.
1749
1750        Arguments:
1751
1752            coord -- (int, int) or str
1753
1754        Return: Cell
1755        """
1756        x, y = self._translate_cell_coordinates(coord)
1757        if x is None:
1758            raise ValueError
1759        if y is None:
1760            raise ValueError
1761        # Outside the defined table
1762        if y >= self.height:
1763            cell = Cell()
1764        else:
1765            # Inside the defined table
1766            row = self._get_row2_base(y)
1767            if row is None:
1768                raise ValueError
1769            read_cell = row.get_cell(x, clone=clone)
1770            if read_cell is None:
1771                raise ValueError
1772            cell = read_cell
1773            if not keep_repeated:
1774                repeated = cell.repeated or 1
1775                if repeated >= 2:
1776                    cell.repeated = None
1777        cell.x = x
1778        cell.y = y
1779        return cell
1780
1781    def get_value(
1782        self,
1783        coord: tuple | list | str,
1784        get_type: bool = False,
1785    ) -> Any:
1786        """Shortcut to get the Python value of the cell at the given
1787        coordinates.
1788
1789        If get_type is True, returns the tuples (value, ODF type)
1790
1791        coord is either a 2-uplet of (x, y) starting from 0, or a
1792        human-readable position like "C4". If an Area is given, the upper
1793        left position is used as coord.
1794
1795        Arguments:
1796
1797            coord -- (int, int) or str : coordinate
1798
1799        Return: Python type
1800        """
1801        x, y = self._translate_cell_coordinates(coord)
1802        if x is None:
1803            raise ValueError
1804        if y is None:
1805            raise ValueError
1806        # Outside the defined table
1807        if y >= self.height:
1808            if get_type:
1809                return (None, None)
1810            return None
1811        else:
1812            # Inside the defined table
1813            row = self._get_row2_base(y)
1814            if row is None:
1815                raise ValueError
1816            cell = row._get_cell2_base(x)
1817            if cell is None:
1818                if get_type:
1819                    return (None, None)
1820                return None
1821            return cell.get_value(get_type=get_type)
1822
1823    def set_cell(
1824        self,
1825        coord: tuple | list | str,
1826        cell: Cell | None = None,
1827        clone: bool = True,
1828    ) -> Cell:
1829        """Replace a cell of the table at the given coordinates.
1830
1831        They are either a 2-uplet of (x, y) starting from 0, or a
1832        human-readable position like "C4".
1833
1834        Arguments:
1835
1836            coord -- (int, int) or str : coordinate
1837
1838            cell -- Cell
1839
1840        return the cell, with x and y updated
1841        """
1842        if cell is None:
1843            cell = Cell()
1844            clone = False
1845        x, y = self._translate_cell_coordinates(coord)
1846        if x is None:
1847            raise ValueError
1848        if y is None:
1849            raise ValueError
1850        cell.x = x
1851        cell.y = y
1852        if y >= self.height:
1853            row = Row()
1854            cell_back = row.set_cell(x, cell, clone=clone)
1855            self.set_row(y, row, clone=False)
1856        else:
1857            row_read = self._get_row2_base(y)
1858            if row_read is None:
1859                raise ValueError
1860            row = row_read
1861            row.y = y
1862            repeated = row.repeated or 1
1863            if repeated > 1:
1864                row = row.clone
1865                row.repeated = None
1866                cell_back = row.set_cell(x, cell, clone=clone)
1867                self.set_row(y, row, clone=False)
1868            else:
1869                cell_back = row.set_cell(x, cell, clone=clone)
1870                # Update width if necessary, since we don't use set_row
1871                self._update_width(row)
1872        return cell_back
1873
1874    def set_cells(
1875        self,
1876        cells: list[list[Cell]] | list[tuple[Cell]],
1877        coord: tuple | list | str | None = None,
1878        clone: bool = True,
1879    ) -> None:
1880        """Set the cells in the table, from the 'coord' position.
1881
1882        'coord' is the coordinate of the upper left cell to be modified by
1883        values. If 'coord' is None, default to the position (0,0) ("A1").
1884        If 'coord' is an area (e.g. "A2:B5"), the upper left position of this
1885        area is used as coordinate.
1886
1887        The table is *not* cleared before the operation, to reset the table
1888        before setting cells, use table.clear().
1889
1890        A list of lists is expected, with as many lists as rows to be set, and
1891        as many cell in each sublist as cells to be setted in the row.
1892
1893        Arguments:
1894
1895            cells -- list of list of cells
1896
1897            coord -- tuple or str
1898
1899            values -- list of lists of python types
1900        """
1901        if coord:
1902            x, y = self._translate_cell_coordinates(coord)
1903        else:
1904            x = y = 0
1905        if y is None:
1906            y = 0
1907        if x is None:
1908            x = 0
1909        y -= 1
1910        for row_cells in cells:
1911            y += 1
1912            if not row_cells:
1913                continue
1914            row = self.get_row(y, clone=True)
1915            repeated = row.repeated or 1
1916            if repeated >= 2:
1917                row.repeated = None
1918            row.set_cells(row_cells, start=x, clone=clone)
1919            self.set_row(y, row, clone=False)
1920            self._update_width(row)
1921
1922    def set_value(
1923        self,
1924        coord: tuple | list | str,
1925        value: Any,
1926        cell_type: str | None = None,
1927        currency: str | None = None,
1928        style: str | None = None,
1929    ) -> None:
1930        """Set the Python value of the cell at the given coordinates.
1931
1932        They are either a 2-uplet of (x, y) starting from 0, or a
1933        human-readable position like "C4".
1934
1935        Arguments:
1936
1937            coord -- (int, int) or str
1938
1939            value -- Python type
1940
1941            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1942                     'string' or 'time'
1943
1944            currency -- three-letter str
1945
1946            style -- str
1947
1948        """
1949        self.set_cell(
1950            coord,
1951            Cell(value, cell_type=cell_type, currency=currency, style=style),
1952            clone=False,
1953        )
1954
1955    def set_cell_image(
1956        self,
1957        coord: tuple | list | str,
1958        image_frame: Frame,
1959        doc_type: str | None = None,
1960    ) -> None:
1961        """Do all the magic to display an image in the cell at the given
1962        coordinates.
1963
1964        They are either a 2-uplet of (x, y) starting from 0, or a
1965        human-readable position like "C4".
1966
1967        The frame element must contain the expected image position and
1968        dimensions.
1969
1970        DrawImage insertion depends on the document type, so the type must be
1971        provided or the table element must be already attached to a document.
1972
1973        Arguments:
1974
1975            coord -- (int, int) or str
1976
1977            image_frame -- Frame including an image
1978
1979            doc_type -- 'spreadsheet' or 'text'
1980        """
1981        # Test document type
1982        if doc_type is None:
1983            body = self.document_body
1984            if body is None:
1985                raise ValueError("document type not found")
1986            doc_type = {"office:spreadsheet": "spreadsheet", "office:text": "text"}.get(
1987                body.tag
1988            )
1989            if doc_type is None:
1990                raise ValueError("document type not supported for images")
1991        # We need the end address of the image
1992        x, y = self._translate_cell_coordinates(coord)
1993        if x is None:
1994            raise ValueError
1995        if y is None:
1996            raise ValueError
1997        cell = self.get_cell((x, y))
1998        image_frame = image_frame.clone  # type: ignore
1999        # Remove any previous paragraph, frame, etc.
2000        for child in cell.children:
2001            cell.delete(child)
2002        # Now it all depends on the document type
2003        if doc_type == "spreadsheet":
2004            image_frame.anchor_type = "char"
2005            # The frame needs end coordinates
2006            width, height = image_frame.size
2007            image_frame.set_attribute("table:end-x", width)
2008            image_frame.set_attribute("table:end-y", height)
2009            # FIXME what happens when the address changes?
2010            address = f"{self.name}.{digit_to_alpha(x)}{y + 1}"
2011            image_frame.set_attribute("table:end-cell-address", address)
2012            # The frame is directly in the cell
2013            cell.append(image_frame)
2014        elif doc_type == "text":
2015            # The frame must be in a paragraph
2016            cell.set_value("")
2017            paragraph = cell.get_element("text:p")
2018            if paragraph is None:
2019                raise ValueError
2020            paragraph.append(image_frame)
2021        self.set_cell(coord, cell)
2022
2023    def insert_cell(
2024        self,
2025        coord: tuple | list | str,
2026        cell: Cell | None = None,
2027        clone: bool = True,
2028    ) -> Cell:
2029        """Insert the given cell at the given coordinates. If no cell is
2030        given, an empty one is created.
2031
2032        Coordinates are either a 2-uplet of (x, y) starting from 0, or a
2033        human-readable position like "C4".
2034
2035        Cells on the right are shifted. Other rows remain untouched.
2036
2037        Arguments:
2038
2039            coord -- (int, int) or str
2040
2041            cell -- Cell
2042
2043        returns the cell with x and y updated
2044        """
2045        if cell is None:
2046            cell = Cell()
2047            clone = False
2048        if clone:
2049            cell = cell.clone
2050        x, y = self._translate_cell_coordinates(coord)
2051        if x is None:
2052            raise ValueError
2053        if y is None:
2054            raise ValueError
2055        row = self._get_row2(y, clone=True)
2056        row.y = y
2057        row.repeated = None
2058        cell_back = row.insert_cell(x, cell, clone=False)
2059        self.set_row(y, row, clone=False)
2060        # Update width if necessary
2061        self._update_width(row)
2062        return cell_back
2063
2064    def append_cell(
2065        self,
2066        y: int | str,
2067        cell: Cell | None = None,
2068        clone: bool = True,
2069    ) -> Cell:
2070        """Append the given cell at the "y" coordinate. Repeated cells are
2071        accepted. If no cell is given, an empty one is created.
2072
2073        Position start at 0. So cell A4 is on row 3.
2074
2075        Other rows remain untouched.
2076
2077        Arguments:
2078
2079            y -- int or str
2080
2081            cell -- Cell
2082
2083        returns the cell with x and y updated
2084        """
2085        if cell is None:
2086            cell = Cell()
2087            clone = False
2088        if clone:
2089            cell = cell.clone
2090        y = self._translate_y_from_any(y)
2091        row = self._get_row2(y)
2092        row.y = y
2093        cell_back = row.append_cell(cell, clone=False)
2094        self.set_row(y, row)
2095        # Update width if necessary
2096        self._update_width(row)
2097        return cell_back
2098
2099    def delete_cell(self, coord: tuple | list | str) -> None:
2100        """Delete the cell at the given coordinates, so that next cells are
2101        shifted to the left.
2102
2103        Coordinates are either a 2-uplet of (x, y) starting from 0, or a
2104        human-readable position like "C4".
2105
2106        Use set_value() for erasing value.
2107
2108        Arguments:
2109
2110            coord -- (int, int) or str
2111        """
2112        x, y = self._translate_cell_coordinates(coord)
2113        if x is None:
2114            raise ValueError
2115        if y is None:
2116            raise ValueError
2117        # Outside the defined table
2118        if y >= self.height:
2119            return
2120        # Inside the defined table
2121        row = self._get_row2_base(y)
2122        if row is None:
2123            raise ValueError
2124        row.delete_cell(x)
2125        # self.set_row(y, row)
2126
2127    # Columns
2128
2129    def _get_columns(self) -> list:
2130        return self.get_elements(_xpath_column)
2131
2132    def traverse_columns(  # noqa: C901
2133        self,
2134        start: int | None = None,
2135        end: int | None = None,
2136    ) -> Iterator[Column]:
2137        """Yield as many column elements as expected columns in the table,
2138        i.e. expand repetitions by returning the same column as many times as
2139        necessary.
2140
2141            Arguments:
2142
2143                start -- int
2144
2145                end -- int
2146
2147        Copies are returned, use set_column() to push them back.
2148        """
2149        idx = -1
2150        before = -1
2151        x = 0
2152        if start is None and end is None:
2153            for juska in self._cmap:
2154                idx += 1
2155                if idx in self._indexes["_cmap"]:
2156                    column = self._indexes["_cmap"][idx]
2157                else:
2158                    column = self._get_element_idx2(_xpath_column_idx, idx)
2159                    self._indexes["_cmap"][idx] = column
2160                repeated = juska - before
2161                before = juska
2162                for _i in range(repeated or 1):
2163                    # Return a copy without the now obsolete repetition
2164                    column = column.clone
2165                    column.x = x
2166                    x += 1
2167                    if repeated > 1:
2168                        column.repeated = None
2169                    yield column
2170        else:
2171            if start is None:
2172                start = 0
2173            start = max(0, start)
2174            if end is None:
2175                try:
2176                    end = self._cmap[-1]
2177                except Exception:
2178                    end = -1
2179            start_map = find_odf_idx(self._cmap, start)
2180            if start_map is None:
2181                return
2182            if start_map > 0:
2183                before = self._cmap[start_map - 1]
2184            idx = start_map - 1
2185            before = start - 1
2186            x = start
2187            for juska in self._cmap[start_map:]:
2188                idx += 1
2189                if idx in self._indexes["_cmap"]:
2190                    column = self._indexes["_cmap"][idx]
2191                else:
2192                    column = self._get_element_idx2(_xpath_column_idx, idx)
2193                    self._indexes["_cmap"][idx] = column
2194                repeated = juska - before
2195                before = juska
2196                for _i in range(repeated or 1):
2197                    if x <= end:
2198                        column = column.clone
2199                        column.x = x
2200                        x += 1
2201                        if repeated > 1 or (x == start and start > 0):
2202                            column.repeated = None
2203                        yield column
2204
2205    def get_columns(
2206        self,
2207        coord: tuple | list | str | None = None,
2208        style: str | None = None,
2209    ) -> list[Column]:
2210        """Get the list of columns matching the criteria. Each result is a
2211        tuple of (x, column).
2212
2213        Arguments:
2214
2215            coord -- str or tuple of int : coordinates of columns
2216
2217            style -- str
2218
2219        Return: list of columns
2220        """
2221        if coord:
2222            x, _y, _z, t = self._translate_column_coordinates(coord)
2223        else:
2224            x = t = None
2225        if not style:
2226            return list(self.traverse_columns(start=x, end=t))
2227        columns = []
2228        for column in self.traverse_columns(start=x, end=t):
2229            if style != column.style:
2230                continue
2231            columns.append(column)
2232        return columns
2233
2234    def _get_column2(self, x: int) -> Column | None:
2235        # Outside the defined table
2236        if x >= self.width:
2237            return Column()
2238        # Inside the defined table
2239        odf_idx = find_odf_idx(self._cmap, x)
2240        if odf_idx is not None:
2241            column = self._get_element_idx2(_xpath_column_idx, odf_idx)
2242            if column is None:
2243                return None
2244            # fixme : no clone here => change doc and unit tests
2245            return column.clone  # type: ignore
2246            # return row
2247        return None
2248
2249    def get_column(self, x: int | str) -> Column:
2250        """Get the column at the given "x" position.
2251
2252        ODF columns don't contain cells, only style information.
2253
2254        Position start at 0. So cell C4 is on column 2. Alphabetical position
2255        like "C" is accepted.
2256
2257        A copy is returned, use set_column() to push it back.
2258
2259        Arguments:
2260
2261            x -- int or str
2262
2263        Return: Column
2264        """
2265        x = self._translate_x_from_any(x)
2266        column = self._get_column2(x)
2267        if column is None:
2268            raise ValueError
2269        column.x = x
2270        return column
2271
2272    def set_column(
2273        self,
2274        x: int | str,
2275        column: Column | None = None,
2276    ) -> Column:
2277        """Replace the column at the given "x" position.
2278
2279        ODF columns don't contain cells, only style information.
2280
2281        Position start at 0. So cell C4 is on column 2. Alphabetical position
2282        like "C" is accepted.
2283
2284        Arguments:
2285
2286            x -- int or str
2287
2288            column -- Column
2289        """
2290        x = self._translate_x_from_any(x)
2291        if column is None:
2292            column = Column()
2293            repeated = 1
2294        else:
2295            repeated = column.repeated or 1
2296        column.x = x
2297        # Outside the defined table ?
2298        diff = x - self.width
2299        if diff == 0:
2300            column_back = self.append_column(column, _repeated=repeated)
2301        elif diff > 0:
2302            self.append_column(Column(repeated=diff), _repeated=diff)
2303            column_back = self.append_column(column, _repeated=repeated)
2304        else:
2305            # Inside the defined table
2306            column_back = set_item_in_vault(  # type: ignore
2307                x, column, self, _xpath_column_idx, "_cmap"
2308            )
2309        return column_back
2310
2311    def insert_column(
2312        self,
2313        x: int | str,
2314        column: Column | None = None,
2315    ) -> Column:
2316        """Insert the column before the given "x" position. If no column is
2317        given, an empty one is created.
2318
2319        ODF columns don't contain cells, only style information.
2320
2321        Position start at 0. So cell C4 is on column 2. Alphabetical position
2322        like "C" is accepted.
2323
2324        Arguments:
2325
2326            x -- int or str
2327
2328            column -- Column
2329        """
2330        if column is None:
2331            column = Column()
2332        x = self._translate_x_from_any(x)
2333        diff = x - self.width
2334        if diff < 0:
2335            column_back = insert_item_in_vault(
2336                x, column, self, _xpath_column_idx, "_cmap"
2337            )
2338        elif diff == 0:
2339            column_back = self.append_column(column.clone)
2340        else:
2341            self.append_column(Column(repeated=diff), _repeated=diff)
2342            column_back = self.append_column(column.clone)
2343        column_back.x = x  # type: ignore
2344        # Repetitions are accepted
2345        repeated = column.repeated or 1
2346        # Update width on every row
2347        for row in self._get_rows():
2348            if row.width > x:
2349                row.insert_cell(x, Cell(repeated=repeated))
2350            # Shorter rows don't need insert
2351            # Longer rows shouldn't exist!
2352        return column_back  # type: ignore
2353
2354    def append_column(
2355        self,
2356        column: Column | None = None,
2357        _repeated: int | None = None,
2358    ) -> Column:
2359        """Append the column at the end of the table. If no column is given,
2360        an empty one is created.
2361
2362        ODF columns don't contain cells, only style information.
2363
2364        Position start at 0. So cell C4 is on column 2. Alphabetical position
2365        like "C" is accepted.
2366
2367        Arguments:
2368
2369            column -- Column
2370        """
2371        if column is None:
2372            column = Column()
2373        else:
2374            column = column.clone
2375        if not self._cmap:
2376            position = 0
2377        else:
2378            odf_idx = len(self._cmap) - 1
2379            last_column = self._get_element_idx2(_xpath_column_idx, odf_idx)
2380            if last_column is None:
2381                raise ValueError
2382            position = self.index(last_column) + 1
2383        column.x = self.width
2384        self.insert(column, position=position)
2385        # Repetitions are accepted
2386        if _repeated is None:
2387            _repeated = column.repeated or 1
2388        self._cmap = insert_map_once(self._cmap, len(self._cmap), _repeated)
2389        # No need to update row widths
2390        return column
2391
2392    def delete_column(self, x: int | str) -> None:
2393        """Delete the column at the given position. ODF columns don't contain
2394        cells, only style information.
2395
2396        Position start at 0. So cell C4 is on column 2. Alphabetical position
2397        like "C" is accepted.
2398
2399        Arguments:
2400
2401            x -- int or str
2402        """
2403        x = self._translate_x_from_any(x)
2404        # Outside the defined table
2405        if x >= self.width:
2406            return
2407        # Inside the defined table
2408        delete_item_in_vault(x, self, _xpath_column_idx, "_cmap")
2409        # Update width
2410        width = self.width
2411        for row in self._get_rows():
2412            if row.width >= width:
2413                row.delete_cell(x)
2414
2415    def get_column_cells(  # noqa: C901
2416        self,
2417        x: int | str,
2418        style: str | None = None,
2419        content: str | None = None,
2420        cell_type: str | None = None,
2421        complete: bool = False,
2422    ) -> list[Cell | None]:
2423        """Get the list of cells at the given position.
2424
2425        Position start at 0. So cell C4 is on column 2. Alphabetical position
2426        like "C" is accepted.
2427
2428        Filter by cell_type, with cell_type 'all' will retrieve cells of any
2429        type, aka non empty cells.
2430
2431        If complete is True, replace missing values by None.
2432
2433        Arguments:
2434
2435            x -- int or str
2436
2437            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
2438                         'currency', 'percentage' or 'all'
2439
2440            content -- str regex
2441
2442            style -- str
2443
2444            complete -- boolean
2445
2446        Return: list of Cell
2447        """
2448        x = self._translate_x_from_any(x)
2449        if cell_type:
2450            cell_type = cell_type.lower().strip()
2451        cells: list[Cell | None] = []
2452        if not style and not content and not cell_type:
2453            for row in self.traverse():
2454                cells.append(row.get_cell(x, clone=True))
2455            return cells
2456        for row in self.traverse():
2457            cell = row.get_cell(x, clone=True)
2458            if cell is None:
2459                raise ValueError
2460            # Filter the cells by cell_type
2461            if cell_type:
2462                ctype = cell.type
2463                if not ctype or not (ctype == cell_type or cell_type == "all"):
2464                    if complete:
2465                        cells.append(None)
2466                    continue
2467            # Filter the cells with the regex
2468            if content and not cell.match(content):
2469                if complete:
2470                    cells.append(None)
2471                continue
2472            # Filter the cells with the style
2473            if style and style != cell.style:
2474                if complete:
2475                    cells.append(None)
2476                continue
2477            cells.append(cell)
2478        return cells
2479
2480    def get_column_values(
2481        self,
2482        x: int | str,
2483        cell_type: str | None = None,
2484        complete: bool = True,
2485        get_type: bool = False,
2486    ) -> list[Any]:
2487        """Shortcut to get the list of Python values for the cells at the
2488        given position.
2489
2490        Position start at 0. So cell C4 is on column 2. Alphabetical position
2491        like "C" is accepted.
2492
2493        Filter by cell_type, with cell_type 'all' will retrieve cells of any
2494        type, aka non empty cells.
2495        If cell_type and complete is True, replace missing values by None.
2496
2497        If get_type is True, returns a tuple (value, ODF type of value)
2498
2499        Arguments:
2500
2501            x -- int or str
2502
2503            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
2504                         'currency', 'percentage' or 'all'
2505
2506            complete -- boolean
2507
2508            get_type -- boolean
2509
2510        Return: list of Python types
2511        """
2512        cells = self.get_column_cells(
2513            x, style=None, content=None, cell_type=cell_type, complete=complete
2514        )
2515        values: list[Any] = []
2516        for cell in cells:
2517            if cell is None:
2518                if complete:
2519                    if get_type:
2520                        values.append((None, None))
2521                    else:
2522                        values.append(None)
2523                continue
2524            if cell_type:
2525                ctype = cell.type
2526                if not ctype or not (ctype == cell_type or cell_type == "all"):
2527                    if complete:
2528                        if get_type:
2529                            values.append((None, None))
2530                        else:
2531                            values.append(None)
2532                    continue
2533            values.append(cell.get_value(get_type=get_type))
2534        return values
2535
2536    def set_column_cells(self, x: int | str, cells: list[Cell]) -> None:
2537        """Shortcut to set the list of cells at the given position.
2538
2539        Position start at 0. So cell C4 is on column 2. Alphabetical position
2540        like "C" is accepted.
2541
2542        The list must have the same length than the table height.
2543
2544        Arguments:
2545
2546            x -- int or str
2547
2548            cells -- list of Cell
2549        """
2550        height = self.height
2551        if len(cells) != height:
2552            raise ValueError(f"col mismatch: {height} cells expected")
2553        cells_iterator = iter(cells)
2554        for y, row in enumerate(self.traverse()):
2555            row.set_cell(x, next(cells_iterator))
2556            self.set_row(y, row)
2557
2558    def set_column_values(
2559        self,
2560        x: int | str,
2561        values: list,
2562        cell_type: str | None = None,
2563        currency: str | None = None,
2564        style: str | None = None,
2565    ) -> None:
2566        """Shortcut to set the list of Python values of cells at the given
2567        position.
2568
2569        Position start at 0. So cell C4 is on column 2. Alphabetical position
2570        like "C" is accepted.
2571
2572        The list must have the same length than the table height.
2573
2574        Arguments:
2575
2576            x -- int or str
2577
2578            values -- list of Python types
2579
2580            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
2581                         'string' or 'time'
2582
2583            currency -- three-letter str
2584
2585            style -- str
2586        """
2587        cells = [
2588            Cell(value, cell_type=cell_type, currency=currency, style=style)
2589            for value in values
2590        ]
2591        self.set_column_cells(x, cells)
2592
2593    def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool:
2594        """Return wether every cell in the column at "x" position has no value
2595        or the value evaluates to False (empty string), and no style.
2596
2597        Position start at 0. So cell C4 is on column 2. Alphabetical position
2598        like "C" is accepted.
2599
2600        If aggressive is True, empty cells with style are considered empty.
2601
2602        Return: bool
2603        """
2604        for cell in self.get_column_cells(x):
2605            if cell is None:
2606                continue
2607            if not cell.is_empty(aggressive=aggressive):
2608                return False
2609        return True
2610
2611    # Named Range
2612
2613    def get_named_ranges(  # type: ignore
2614        self,
2615        table_name: str | list[str] | None = None,
2616    ) -> list[NamedRange]:
2617        """Returns the list of available Name Ranges of the spreadsheet. If
2618        table_name is provided, limits the search to these tables.
2619        Beware : named ranges are stored at the body level, thus do not call
2620        this method on a cloned table.
2621
2622        Arguments:
2623
2624            table_names -- str or list of str, names of tables
2625
2626        Return : list of table_range
2627        """
2628        body = self.document_body
2629        if not body:
2630            return []
2631        all_named_ranges = body.get_named_ranges()
2632        if not table_name:
2633            return all_named_ranges  # type:ignore
2634        filter_ = []
2635        if isinstance(table_name, str):
2636            filter_.append(table_name)
2637        elif isiterable(table_name):
2638            filter_.extend(table_name)
2639        else:
2640            raise ValueError(
2641                f"table_name must be string or Iterable, not {type(table_name)}"
2642            )
2643        return [
2644            nr for nr in all_named_ranges if nr.table_name in filter_  # type:ignore
2645        ]
2646
2647    def get_named_range(self, name: str) -> NamedRange:
2648        """Returns the Name Ranges of the specified name. If
2649        table_name is provided, limits the search to these tables.
2650        Beware : named ranges are stored at the body level, thus do not call
2651        this method on a cloned table.
2652
2653        Arguments:
2654
2655            name -- str, name of the named range object
2656
2657        Return : NamedRange
2658        """
2659        body = self.document_body
2660        if not body:
2661            raise ValueError("Table is not inside a document")
2662        return body.get_named_range(name)  # type: ignore
2663
2664    def set_named_range(
2665        self,
2666        name: str,
2667        crange: str | tuple | list,
2668        table_name: str | None = None,
2669        usage: str | None = None,
2670    ) -> None:
2671        """Create a Named Range element and insert it in the document.
2672        Beware : named ranges are stored at the body level, thus do not call
2673        this method on a cloned table.
2674
2675        Arguments:
2676
2677            name -- str, name of the named range
2678
2679            crange -- str or tuple of int, cell or area coordinate
2680
2681            table_name -- str, name of the table
2682
2683            uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2684        """
2685        body = self.document_body
2686        if not body:
2687            raise ValueError("Table is not inside a document")
2688        if not name:
2689            raise ValueError("Name required.")
2690        if table_name is None:
2691            table_name = self.name
2692        named_range = NamedRange(name, crange, table_name, usage)
2693        body.append_named_range(named_range)
2694
2695    def delete_named_range(self, name: str) -> None:
2696        """Delete the Named Range of specified name from the spreadsheet.
2697        Beware : named ranges are stored at the body level, thus do not call
2698        this method on a cloned table.
2699
2700        Arguments:
2701
2702            name -- str
2703        """
2704        name = name.strip()
2705        if not name:
2706            raise ValueError("Name required.")
2707        body = self.document_body
2708        if not body:
2709            raise ValueError("Table is not inside a document.")
2710        body.delete_named_range(name)
2711
2712    #
2713    # Cell span
2714    #
2715
2716    def set_span(  # noqa: C901
2717        self,
2718        area: str | tuple | list,
2719        merge: bool = False,
2720    ) -> bool:
2721        """Create a Cell Span : span the first cell of the area on several
2722        columns and/or rows.
2723        If merge is True, replace text of the cell by the concatenation of
2724        existing text in covered cells.
2725        Beware : if merge is True, old text is changed, if merge is False
2726        (the default), old text in coverd cells is still present but not
2727        displayed by most GUI.
2728
2729        If the area defines only one cell, the set span will do nothing.
2730        It is not allowed to apply set span to an area whose one cell already
2731        belongs to previous cell span.
2732
2733        Area can be either one cell (like 'A1') or an area ('A1:B2'). It can
2734        be provided as an alpha numeric value like "A1:B2' or a tuple like
2735        (0, 0, 1, 1) or (0, 0).
2736
2737        Arguments:
2738
2739            area -- str or tuple of int, cell or area coordinate
2740
2741            merge -- boolean
2742        """
2743        # get area
2744        digits = convert_coordinates(area)
2745        if len(digits) == 4:
2746            x, y, z, t = digits
2747        else:
2748            x, y = digits
2749            z, t = digits
2750        start = x, y
2751        end = z, t
2752        if start == end:
2753            # one cell : do nothing
2754            return False
2755        if x is None:
2756            raise ValueError
2757        if y is None:
2758            raise ValueError
2759        if z is None:
2760            raise ValueError
2761        if t is None:
2762            raise ValueError
2763        # check for previous span
2764        good = True
2765        # Check boundaries and empty cells : need to crate non existent cells
2766        # so don't use get_cells directly, but get_cell
2767        cells = []
2768        for yy in range(y, t + 1):
2769            row_cells = []
2770            for xx in range(x, z + 1):
2771                row_cells.append(
2772                    self.get_cell((xx, yy), clone=True, keep_repeated=False)
2773                )
2774            cells.append(row_cells)
2775        for row in cells:
2776            for cell in row:
2777                if cell.is_spanned():
2778                    good = False
2779                    break
2780            if not good:
2781                break
2782        if not good:
2783            return False
2784        # Check boundaries
2785        # if z >= self.width or t >= self.height:
2786        #    self.set_cell(coord = end)
2787        #    print area, z, t
2788        #    cells = self.get_cells((x, y, z, t))
2789        #    print cells
2790        # do it:
2791        if merge:
2792            val_list = []
2793            for row in cells:
2794                for cell in row:
2795                    if cell.is_empty(aggressive=True):
2796                        continue
2797                    val = cell.get_value()
2798                    if val is not None:
2799                        if isinstance(val, str):
2800                            val.strip()
2801                        if val != "":
2802                            val_list.append(val)
2803                        cell.clear()
2804            if val_list:
2805                if len(val_list) == 1:
2806                    cells[0][0].set_value(val_list[0])
2807                else:
2808                    value = " ".join([str(v) for v in val_list if v])
2809                    cells[0][0].set_value(value)
2810        cols = z - x + 1
2811        cells[0][0].set_attribute("table:number-columns-spanned", str(cols))
2812        rows = t - y + 1
2813        cells[0][0].set_attribute("table:number-rows-spanned", str(rows))
2814        for cell in cells[0][1:]:
2815            cell.tag = "table:covered-table-cell"
2816        for row in cells[1:]:
2817            for cell in row:
2818                cell.tag = "table:covered-table-cell"
2819        # replace cells in table
2820        self.set_cells(cells, coord=start, clone=False)
2821        return True
2822
2823    def del_span(self, area: str | tuple | list) -> bool:
2824        """Delete a Cell Span. 'area' is the cell coordiante of the upper left
2825        cell of the spanned area.
2826
2827        Area can be either one cell (like 'A1') or an area ('A1:B2'). It can
2828        be provided as an alpha numeric value like "A1:B2' or a tuple like
2829        (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell
2830        is used.
2831
2832        Arguments:
2833
2834            area -- str or tuple of int, cell or area coordinate
2835        """
2836        # get area
2837        digits = convert_coordinates(area)
2838        if len(digits) == 4:
2839            x, y, _z, _t = digits
2840        else:
2841            x, y = digits
2842        if x is None:
2843            raise ValueError
2844        if y is None:
2845            raise ValueError
2846        start = x, y
2847        # check for previous span
2848        cell0 = self.get_cell(start)
2849        nb_cols = cell0.get_attribute_integer("table:number-columns-spanned")
2850        if nb_cols is None:
2851            return False
2852        nb_rows = cell0.get_attribute_integer("table:number-rows-spanned")
2853        if nb_rows is None:
2854            return False
2855        z = x + nb_cols - 1
2856        t = y + nb_rows - 1
2857        cells = self.get_cells((x, y, z, t))
2858        cells[0][0].del_attribute("table:number-columns-spanned")
2859        cells[0][0].del_attribute("table:number-rows-spanned")
2860        for cell in cells[0][1:]:
2861            cell.tag = "table:table-cell"
2862        for row in cells[1:]:
2863            for cell in row:
2864                cell.tag = "table:table-cell"
2865        # replace cells in table
2866        self.set_cells(cells, coord=start, clone=False)
2867        return True
2868
2869    # Utilities
2870
2871    def to_csv(
2872        self,
2873        path_or_file: str | Path | None = None,
2874        dialect: str = "excel",
2875    ) -> Any:
2876        """Write the table as CSV in the file.
2877
2878        If the file is a string, it is opened as a local path. Else an
2879        opened file-like is expected.
2880
2881        Arguments:
2882
2883            path_or_file -- str or file-like
2884
2885            dialect -- str, python csv.dialect, can be 'excel', 'unix'...
2886        """
2887
2888        def write_content(csv_writer: object) -> None:
2889            for values in self.iter_values():
2890                line = []
2891                for value in values:
2892                    if value is None:
2893                        value = ""
2894                    if isinstance(value, str):
2895                        value = value.strip()
2896                    line.append(value)
2897                csv_writer.writerow(line)  # type: ignore
2898
2899        out = StringIO(newline="")
2900        csv_writer = csv.writer(out, dialect=dialect)
2901        write_content(csv_writer)
2902        if path_or_file is None:
2903            return out.getvalue()
2904        path = Path(path_or_file)
2905        path.write_text(out.getvalue())
2906        return None

ODF table "table:table"

Table( name: str | None = None, width: int | None = None, height: int | None = None, protected: bool = False, protection_key: str | None = None, display: bool = True, printable: bool = True, print_ranges: list[str] | None = None, style: str | None = None, **kwargs: Any)
294    def __init__(
295        self,
296        name: str | None = None,
297        width: int | None = None,
298        height: int | None = None,
299        protected: bool = False,
300        protection_key: str | None = None,
301        display: bool = True,
302        printable: bool = True,
303        print_ranges: list[str] | None = None,
304        style: str | None = None,
305        **kwargs: Any,
306    ) -> None:
307        """Create a table element, optionally prefilled with "height" rows of
308        "width" cells each.
309
310        If the table is to be protected, a protection key must be provided,
311        i.e. a hash value of the password.
312
313        If the table must not be displayed, set "display" to False.
314
315        If the table must not be printed, set "printable" to False. The table
316        will not be printed when it is not displayed, whatever the value of
317        this argument.
318
319        Ranges of cells to print can be provided as a list of cell ranges,
320        e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g.
321        "E6:K12 P6:R12".
322
323        You can access and modify the XML tree manually, but you probably want
324        to use the API to access and alter cells. It will save you from
325        handling repetitions and the same number of cells for each row.
326
327        If you use both the table API and the XML API, you are on your own for
328        ensuiring model integrity.
329
330        Arguments:
331
332            name -- str
333
334            width -- int
335
336            height -- int
337
338            protected -- bool
339
340            protection_key -- str
341
342            display -- bool
343
344            printable -- bool
345
346            print_ranges -- list
347
348            style -- str
349        """
350        super().__init__(**kwargs)
351        self._indexes = {}
352        self._indexes["_cmap"] = {}
353        self._indexes["_tmap"] = {}
354        if self._do_init:
355            self.name = name
356            if protected:
357                self.protected = protected
358                self.set_protection_key = protection_key
359            if not display:
360                self.displayed = display
361            if not printable:
362                self.printable = printable
363            if print_ranges:
364                self.print_ranges = print_ranges
365            if style:
366                self.style = style
367            # Prefill the table
368            if width is not None or height is not None:
369                width = width or 1
370                height = height or 1
371                # Column groups for style information
372                columns = Column(repeated=width)
373                self._append(columns)
374                for _i in range(height):
375                    row = Row(width)
376                    self._append(row)
377        self._compute_table_cache()

Create a table element, optionally prefilled with "height" rows of "width" cells each.

If the table is to be protected, a protection key must be provided, i.e. a hash value of the password.

If the table must not be displayed, set "display" to False.

If the table must not be printed, set "printable" to False. The table will not be printed when it is not displayed, whatever the value of this argument.

Ranges of cells to print can be provided as a list of cell ranges, e.g. ['E6:K12', 'P6:R12'] or directly as a raw string, e.g. "E6:K12 P6:R12".

You can access and modify the XML tree manually, but you probably want to use the API to access and alter cells. It will save you from handling repetitions and the same number of cells for each row.

If you use both the table API and the XML API, you are on your own for ensuiring model integrity.

Arguments:

name -- str

width -- int

height -- int

protected -- bool

protection_key -- str

display -- bool

printable -- bool

print_ranges -- list

style -- str
def append(self, something: Element | str) -> None:
734    def append(self, something: Element | str) -> None:
735        """Dispatch .append() call to append_row() or append_column()."""
736        if isinstance(something, Row):
737            self.append_row(something)
738        elif isinstance(something, Column):
739            self.append_column(something)
740        else:
741            # probably still an error
742            self._append(something)

Dispatch .append() call to append_row() or append_column().

height: int
744    @property
745    def height(self) -> int:
746        """Get the current height of the table.
747
748        Return: int
749        """
750        try:
751            height = self._tmap[-1] + 1
752        except Exception:
753            height = 0
754        return height

Get the current height of the table.

Return: int

width: int
756    @property
757    def width(self) -> int:
758        """Get the current width of the table, measured on columns.
759
760        Rows may have different widths, use the Table API to ensure width
761        consistency.
762
763        Return: int
764        """
765        # Columns are our reference for user expected width
766
767        try:
768            width = self._cmap[-1] + 1
769        except Exception:
770            width = 0
771
772        # columns = self._get_columns()
773        # repeated = self.xpath(
774        #        'table:table-column/@table:number-columns-repeated')
775        # unrepeated = len(columns) - len(repeated)
776        # ws = sum(int(r) for r in repeated) + unrepeated
777        # if w != ws:
778        #    print "WARNING   ws", ws, "w", w
779
780        return width

Get the current width of the table, measured on columns.

Rows may have different widths, use the Table API to ensure width consistency.

Return: int

size: tuple[int, int]
782    @property
783    def size(self) -> tuple[int, int]:
784        """Shortcut to get the current width and height of the table.
785
786        Return: (int, int)
787        """
788        return self.width, self.height

Shortcut to get the current width and height of the table.

Return: (int, int)

name: str | None
790    @property
791    def name(self) -> str | None:
792        """Get / set the name of the table."""
793        return self.get_attribute_string("table:name")

Get / set the name of the table.

protected: bool
804    @property
805    def protected(self) -> bool:
806        return bool(self.get_attribute("table:protected"))
protection_key: str | None
812    @property
813    def protection_key(self) -> str | None:
814        return self.get_attribute_string("table:protection-key")
displayed: bool
820    @property
821    def displayed(self) -> bool:
822        return bool(self.get_attribute("table:display"))
printable: bool
828    @property
829    def printable(self) -> bool:
830        printable = self.get_attribute("table:print")
831        # Default value
832        if printable is None:
833            return True
834        return bool(printable)
print_ranges: list[str]
840    @property
841    def print_ranges(self) -> list[str]:
842        ranges = self.get_attribute_string("table:print-ranges")
843        if isinstance(ranges, str):
844            return ranges.split()
845        return []
style: str | None
854    @property
855    def style(self) -> str | None:
856        """Get / set the style of the table
857
858        Return: str
859        """
860        return self.get_attribute_string("table:style-name")

Get / set the style of the table

Return: str

def get_formatted_text(self, context: dict | None = None) -> str:
866    def get_formatted_text(self, context: dict | None = None) -> str:
867        if context and context["rst_mode"]:
868            return self._get_formatted_text_rst(context)
869        return self._get_formatted_text_normal(context)

This function should return a beautiful version of the text.

def get_values( self, coord: tuple | list | str | None = None, cell_type: str | None = None, complete: bool = True, get_type: bool = False, flat: bool = False) -> list:
871    def get_values(
872        self,
873        coord: tuple | list | str | None = None,
874        cell_type: str | None = None,
875        complete: bool = True,
876        get_type: bool = False,
877        flat: bool = False,
878    ) -> list:
879        """Get a matrix of values of the table.
880
881        Filter by coordinates will parse the area defined by the coordinates.
882
883        If 'cell_type' is used and 'complete' is True (default), missing values
884        are replaced by None.
885        Filter by ' cell_type = "all" ' will retrieve cells of any
886        type, aka non empty cells.
887
888        If 'cell_type' is None, complete is always True : with no cell type
889        queried, get_values() returns None for each empty cell, the length
890        each lists is equal to the width of the table.
891
892        If get_type is True, returns tuples (value, ODF type of value), or
893        (None, None) for empty cells with complete True.
894
895        If flat is True, the methods return a single list of all the values.
896        By default, flat is False.
897
898        Arguments:
899
900            coord -- str or tuple of int : coordinates of area
901
902            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
903                         'currency', 'percentage' or 'all'
904
905            complete -- boolean
906
907            get_type -- boolean
908
909        Return: list of lists of Python types
910        """
911        if coord:
912            x, y, z, t = self._translate_table_coordinates(coord)
913        else:
914            x = y = z = t = None
915        data = []
916        for row in self.traverse(start=y, end=t):
917            if z is None:
918                width = self.width
919            else:
920                width = min(z + 1, self.width)
921            if x is not None:
922                width -= x
923            values = row.get_values(
924                (x, z),
925                cell_type=cell_type,
926                complete=complete,
927                get_type=get_type,
928            )
929            # complete row to match request width
930            if complete:
931                if get_type:
932                    values.extend([(None, None)] * (width - len(values)))
933                else:
934                    values.extend([None] * (width - len(values)))
935            if flat:
936                data.extend(values)
937            else:
938                data.append(values)
939        return data

Get a matrix of values of the table.

Filter by coordinates will parse the area defined by the coordinates.

If 'cell_type' is used and 'complete' is True (default), missing values are replaced by None. Filter by ' cell_type = "all" ' will retrieve cells of any type, aka non empty cells.

If 'cell_type' is None, complete is always True : with no cell type queried, get_values() returns None for each empty cell, the length each lists is equal to the width of the table.

If get_type is True, returns tuples (value, ODF type of value), or (None, None) for empty cells with complete True.

If flat is True, the methods return a single list of all the values. By default, flat is False.

Arguments:

coord -- str or tuple of int : coordinates of area

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: list of lists of Python types

def iter_values( self, coord: tuple | list | str | None = None, cell_type: str | None = None, complete: bool = True, get_type: bool = False) -> collections.abc.Iterator[list]:
941    def iter_values(
942        self,
943        coord: tuple | list | str | None = None,
944        cell_type: str | None = None,
945        complete: bool = True,
946        get_type: bool = False,
947    ) -> Iterator[list]:
948        """Iterate through lines of Python values of the table.
949
950        Filter by coordinates will parse the area defined by the coordinates.
951
952        cell_type, complete, grt_type : see get_values()
953
954
955
956        Arguments:
957
958            coord -- str or tuple of int : coordinates of area
959
960            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
961                         'currency', 'percentage' or 'all'
962
963            complete -- boolean
964
965            get_type -- boolean
966
967        Return: iterator of lists
968        """
969        if coord:
970            x, y, z, t = self._translate_table_coordinates(coord)
971        else:
972            x = y = z = t = None
973        for row in self.traverse(start=y, end=t):
974            if z is None:
975                width = self.width
976            else:
977                width = min(z + 1, self.width)
978            if x is not None:
979                width -= x
980            values = row.get_values(
981                (x, z),
982                cell_type=cell_type,
983                complete=complete,
984                get_type=get_type,
985            )
986            # complete row to match column width
987            if complete:
988                if get_type:
989                    values.extend([(None, None)] * (width - len(values)))
990                else:
991                    values.extend([None] * (width - len(values)))
992            yield values

Iterate through lines of Python values of the table.

Filter by coordinates will parse the area defined by the coordinates.

cell_type, complete, grt_type : see get_values()

Arguments:

coord -- str or tuple of int : coordinates of area

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: iterator of lists

def set_values( self, values: list, coord: tuple | list | str | None = None, style: str | None = None, cell_type: str | None = None, currency: str | None = None) -> None:
 994    def set_values(
 995        self,
 996        values: list,
 997        coord: tuple | list | str | None = None,
 998        style: str | None = None,
 999        cell_type: str | None = None,
1000        currency: str | None = None,
1001    ) -> None:
1002        """Set the value of cells in the table, from the 'coord' position
1003        with values.
1004
1005        'coord' is the coordinate of the upper left cell to be modified by
1006        values. If 'coord' is None, default to the position (0,0) ("A1").
1007        If 'coord' is an area (e.g. "A2:B5"), the upper left position of this
1008        area is used as coordinate.
1009
1010        The table is *not* cleared before the operation, to reset the table
1011        before setting values, use table.clear().
1012
1013        A list of lists is expected, with as many lists as rows, and as many
1014        items in each sublist as cells to be setted. None values in the list
1015        will create empty cells with no cell type (but eventually a style).
1016
1017        Arguments:
1018
1019            coord -- tuple or str
1020
1021            values -- list of lists of python types
1022
1023            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1024                         'string' or 'time'
1025
1026            currency -- three-letter str
1027
1028            style -- str
1029        """
1030        if coord:
1031            x, y = self._translate_cell_coordinates(coord)
1032        else:
1033            x = y = 0
1034        if y is None:
1035            y = 0
1036        if x is None:
1037            x = 0
1038        y -= 1
1039        for row_values in values:
1040            y += 1
1041            if not row_values:
1042                continue
1043            row = self.get_row(y, clone=True)
1044            repeated = row.repeated or 1
1045            if repeated >= 2:
1046                row.repeated = None
1047            row.set_values(
1048                row_values,
1049                start=x,
1050                cell_type=cell_type,
1051                currency=currency,
1052                style=style,
1053            )
1054            self.set_row(y, row, clone=False)
1055            self._update_width(row)

Set the value of cells in the table, from the 'coord' position with values.

'coord' is the coordinate of the upper left cell to be modified by values. If 'coord' is None, default to the position (0,0) ("A1"). If 'coord' is an area (e.g. "A2:B5"), the upper left position of this area is used as coordinate.

The table is not cleared before the operation, to reset the table before setting values, use table.clear().

A list of lists is expected, with as many lists as rows, and as many items in each sublist as cells to be setted. None values in the list will create empty cells with no cell type (but eventually a style).

Arguments:

coord -- tuple or str

values -- list of lists of python types

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
             'string' or 'time'

currency -- three-letter str

style -- str
def rstrip(self, aggressive: bool = False) -> None:
1057    def rstrip(self, aggressive: bool = False) -> None:
1058        """Remove *in-place* empty rows below and empty cells at the right of
1059        the table. Cells are empty if they contain no value or it evaluates
1060        to False, and no style.
1061
1062        If aggressive is True, empty cells with style are removed too.
1063
1064        Argument:
1065
1066            aggressive -- bool
1067        """
1068        # Step 1: remove empty rows below the table
1069        for row in reversed(self._get_rows()):
1070            if row.is_empty(aggressive=aggressive):
1071                row.parent.delete(row)  # type: ignore
1072            else:
1073                break
1074        # Step 2: rstrip remaining rows
1075        max_width = 0
1076        for row in self._get_rows():
1077            row.rstrip(aggressive=aggressive)
1078            # keep count of the biggest row
1079            max_width = max(max_width, row.width)
1080        # raz cache of rows
1081        self._indexes["_tmap"] = {}
1082        # Step 3: trim columns to match max_width
1083        columns = self._get_columns()
1084        repeated_cols = self.xpath("table:table-column/@table:number-columns-repeated")
1085        if not isinstance(repeated_cols, list):
1086            raise TypeError
1087        unrepeated = len(columns) - len(repeated_cols)
1088        column_width = sum(int(r) for r in repeated_cols) + unrepeated  # type: ignore
1089        diff = column_width - max_width
1090        if diff > 0:
1091            for column in reversed(columns):
1092                repeated = column.repeated or 1
1093                repeated = repeated - diff
1094                if repeated > 0:
1095                    column.repeated = repeated
1096                    break
1097                else:
1098                    column.parent.delete(column)
1099                    diff = -repeated
1100                    if diff == 0:
1101                        break
1102        # raz cache of columns
1103        self._indexes["_cmap"] = {}
1104        self._compute_table_cache()

Remove in-place empty rows below and empty cells at the right of the table. Cells are empty if they contain no value or it evaluates to False, and no style.

If aggressive is True, empty cells with style are removed too.

Argument:

aggressive -- bool
def optimize_width(self) -> None:
1106    def optimize_width(self) -> None:
1107        """Remove *in-place* empty rows below and empty cells at the right of
1108        the table. Keep repeated styles of empty cells but minimize row width.
1109        """
1110        self._optimize_width_trim_rows()
1111        width = self._optimize_width_length()
1112        self._optimize_width_rstrip_rows(width)
1113        self._optimize_width_adapt_columns(width)

Remove in-place empty rows below and empty cells at the right of the table. Keep repeated styles of empty cells but minimize row width.

def transpose(self, coord: tuple | list | str | None = None) -> None:
1168    def transpose(self, coord: tuple | list | str | None = None) -> None:  # noqa: C901
1169        """Swap *in-place* rows and columns of the table.
1170
1171        If 'coord' is not None, apply transpose only to the area defined by the
1172        coordinates. Beware, if area is not square, some cells mays be over
1173        written during the process.
1174
1175        Arguments:
1176
1177            coord -- str or tuple of int : coordinates of area
1178
1179            start -- int or str
1180        """
1181        data = []
1182        if coord is None:
1183            for row in self.traverse():
1184                data.append(list(row.traverse()))
1185            transposed_data = zip_longest(*data)
1186            self.clear()
1187            # new_rows = []
1188            for row_cells in transposed_data:
1189                if not isiterable(row_cells):
1190                    row_cells = (row_cells,)
1191                row = Row()
1192                row.extend_cells(row_cells)
1193                self.append_row(row, clone=False)
1194            self._compute_table_cache()
1195        else:
1196            x, y, z, t = self._translate_table_coordinates(coord)
1197            if x is None:
1198                x = 0
1199            else:
1200                x = min(x, self.width - 1)
1201            if z is None:
1202                z = self.width - 1
1203            else:
1204                z = min(z, self.width - 1)
1205            if y is None:
1206                y = 0
1207            else:
1208                y = min(y, self.height - 1)
1209            if t is None:
1210                t = self.height - 1
1211            else:
1212                t = min(t, self.height - 1)
1213            for row in self.traverse(start=y, end=t):
1214                data.append(list(row.traverse(start=x, end=z)))
1215            transposed_data = zip_longest(*data)
1216            # clear locally
1217            w = z - x + 1
1218            h = t - y + 1
1219            if w != h:
1220                nones = [[None] * w for i in range(h)]
1221                self.set_values(nones, coord=(x, y, z, t))
1222            # put transposed
1223            filtered_data: list[tuple[Cell]] = []
1224            for row_cells in transposed_data:
1225                if isinstance(row_cells, (list, tuple)):
1226                    filtered_data.append(row_cells)
1227                else:
1228                    filtered_data.append((row_cells,))
1229            self.set_cells(filtered_data, (x, y, x + h - 1, y + w - 1))
1230            self._compute_table_cache()

Swap in-place rows and columns of the table.

If 'coord' is not None, apply transpose only to the area defined by the coordinates. Beware, if area is not square, some cells mays be over written during the process.

Arguments:

coord -- str or tuple of int : coordinates of area

start -- int or str
def is_empty(self, aggressive: bool = False) -> bool:
1232    def is_empty(self, aggressive: bool = False) -> bool:
1233        """Return whether every cell in the table has no value or the value
1234        evaluates to False (empty string), and no style.
1235
1236        If aggressive is True, empty cells with style are considered empty.
1237
1238        Arguments:
1239
1240            aggressive -- bool
1241        """
1242        return all(row.is_empty(aggressive=aggressive) for row in self._get_rows())

Return whether every cell in the table has no value or the value evaluates to False (empty string), and no style.

If aggressive is True, empty cells with style are considered empty.

Arguments:

aggressive -- bool
def traverse( self, start: int | None = None, end: int | None = None) -> collections.abc.Iterator[Row]:
1251    def traverse(  # noqa: C901
1252        self,
1253        start: int | None = None,
1254        end: int | None = None,
1255    ) -> Iterator[Row]:
1256        """Yield as many row elements as expected rows in the table, i.e.
1257        expand repetitions by returning the same row as many times as
1258        necessary.
1259
1260            Arguments:
1261
1262                start -- int
1263
1264                end -- int
1265
1266        Copies are returned, use set_row() to push them back.
1267        """
1268        idx = -1
1269        before = -1
1270        y = 0
1271        if start is None and end is None:
1272            for juska in self._tmap:
1273                idx += 1
1274                if idx in self._indexes["_tmap"]:
1275                    row = self._indexes["_tmap"][idx]
1276                else:
1277                    row = self._get_element_idx2(_xpath_row_idx, idx)
1278                    self._indexes["_tmap"][idx] = row
1279                repeated = juska - before
1280                before = juska
1281                for _i in range(repeated or 1):
1282                    # Return a copy without the now obsolete repetition
1283                    row = row.clone
1284                    row.y = y
1285                    y += 1
1286                    if repeated > 1:
1287                        row.repeated = None
1288                    yield row
1289        else:
1290            if start is None:
1291                start = 0
1292            start = max(0, start)
1293            if end is None:
1294                try:
1295                    end = self._tmap[-1]
1296                except Exception:
1297                    end = -1
1298            start_map = find_odf_idx(self._tmap, start)
1299            if start_map is None:
1300                return
1301            if start_map > 0:
1302                before = self._tmap[start_map - 1]
1303            idx = start_map - 1
1304            before = start - 1
1305            y = start
1306            for juska in self._tmap[start_map:]:
1307                idx += 1
1308                if idx in self._indexes["_tmap"]:
1309                    row = self._indexes["_tmap"][idx]
1310                else:
1311                    row = self._get_element_idx2(_xpath_row_idx, idx)
1312                    self._indexes["_tmap"][idx] = row
1313                repeated = juska - before
1314                before = juska
1315                for _i in range(repeated or 1):
1316                    if y <= end:
1317                        row = row.clone
1318                        row.y = y
1319                        y += 1
1320                        if repeated > 1 or (y == start and start > 0):
1321                            row.repeated = None
1322                        yield row

Yield as many row elements as expected rows in the table, i.e. expand repetitions by returning the same row as many times as necessary.

Arguments:

    start -- int

    end -- int

Copies are returned, use set_row() to push them back.

def get_rows( self, coord: tuple | list | str | None = None, style: str | None = None, content: str | None = None) -> list[Row]:
1324    def get_rows(
1325        self,
1326        coord: tuple | list | str | None = None,
1327        style: str | None = None,
1328        content: str | None = None,
1329    ) -> list[Row]:
1330        """Get the list of rows matching the criteria.
1331
1332        Filter by coordinates will parse the area defined by the coordinates.
1333
1334        Arguments:
1335
1336            coord -- str or tuple of int : coordinates of rows
1337
1338            content -- str regex
1339
1340            style -- str
1341
1342        Return: list of rows
1343        """
1344        if coord:
1345            _x, y, _z, t = self._translate_table_coordinates(coord)
1346        else:
1347            y = t = None
1348        # fixme : not clones ?
1349        if not content and not style:
1350            return list(self.traverse(start=y, end=t))
1351        rows = []
1352        for row in self.traverse(start=y, end=t):
1353            if content and not row.match(content):
1354                continue
1355            if style and style != row.style:
1356                continue
1357            rows.append(row)
1358        return rows

Get the list of rows matching the criteria.

Filter by coordinates will parse the area defined by the coordinates.

Arguments:

coord -- str or tuple of int : coordinates of rows

content -- str regex

style -- str

Return: list of rows

def get_row( self, y: int | str, clone: bool = True, create: bool = True) -> Row:
1383    def get_row(self, y: int | str, clone: bool = True, create: bool = True) -> Row:
1384        """Get the row at the given "y" position.
1385
1386        Position start at 0. So cell A4 is on row 3.
1387
1388        A copy is returned, use set_cell() to push it back.
1389
1390        Arguments:
1391
1392            y -- int or str
1393
1394        Return: Row
1395        """
1396        # fixme : keep repeat ? maybe an option to functions : "raw=False"
1397        y = self._translate_y_from_any(y)
1398        row = self._get_row2(y, clone=clone, create=create)
1399        if row is None:
1400            raise ValueError("Row not found")
1401        row.y = y
1402        return row

Get the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

A copy is returned, use set_cell() to push it back.

Arguments:

y -- int or str

Return: Row

def set_row( self, y: int | str, row: Row | None = None, clone: bool = True) -> Row:
1404    def set_row(self, y: int | str, row: Row | None = None, clone: bool = True) -> Row:
1405        """Replace the row at the given position with the new one. Repetions of
1406        the old row will be adjusted.
1407
1408        If row is None, a new empty row is created.
1409
1410        Position start at 0. So cell A4 is on row 3.
1411
1412        Arguments:
1413
1414            y -- int or str
1415
1416            row -- Row
1417
1418        returns the row, with updated row.y
1419        """
1420        if row is None:
1421            row = Row()
1422            repeated = 1
1423            clone = False
1424        else:
1425            repeated = row.repeated or 1
1426        y = self._translate_y_from_any(y)
1427        row.y = y
1428        # Outside the defined table ?
1429        diff = y - self.height
1430        if diff == 0:
1431            row_back = self.append_row(row, _repeated=repeated, clone=clone)
1432        elif diff > 0:
1433            self.append_row(Row(repeated=diff), _repeated=diff, clone=clone)
1434            row_back = self.append_row(row, _repeated=repeated, clone=clone)
1435        else:
1436            # Inside the defined table
1437            row_back = set_item_in_vault(  # type: ignore
1438                y, row, self, _xpath_row_idx, "_tmap", clone=clone
1439            )
1440        # print self.serialize(True)
1441        # Update width if necessary
1442        self._update_width(row_back)
1443        return row_back

Replace the row at the given position with the new one. Repetions of the old row will be adjusted.

If row is None, a new empty row is created.

Position start at 0. So cell A4 is on row 3.

Arguments:

y -- int or str

row -- Row

returns the row, with updated row.y

def insert_row( self, y: str | int, row: Row | None = None, clone: bool = True) -> Row:
1445    def insert_row(
1446        self, y: str | int, row: Row | None = None, clone: bool = True
1447    ) -> Row:
1448        """Insert the row before the given "y" position. If no row is given,
1449        an empty one is created.
1450
1451        Position start at 0. So cell A4 is on row 3.
1452
1453        If row is None, a new empty row is created.
1454
1455        Arguments:
1456
1457            y -- int or str
1458
1459            row -- Row
1460
1461        returns the row, with updated row.y
1462        """
1463        if row is None:
1464            row = Row()
1465            clone = False
1466        y = self._translate_y_from_any(y)
1467        diff = y - self.height
1468        if diff < 0:
1469            row_back = insert_item_in_vault(y, row, self, _xpath_row_idx, "_tmap")
1470        elif diff == 0:
1471            row_back = self.append_row(row, clone=clone)
1472        else:
1473            self.append_row(Row(repeated=diff), _repeated=diff, clone=False)
1474            row_back = self.append_row(row, clone=clone)
1475        row_back.y = y  # type: ignore
1476        # Update width if necessary
1477        self._update_width(row_back)  # type: ignore
1478        return row_back  # type: ignore

Insert the row before the given "y" position. If no row is given, an empty one is created.

Position start at 0. So cell A4 is on row 3.

If row is None, a new empty row is created.

Arguments:

y -- int or str

row -- Row

returns the row, with updated row.y

def extend_rows(self, rows: list[Row] | None = None) -> None:
1480    def extend_rows(self, rows: list[Row] | None = None) -> None:
1481        """Append a list of rows at the end of the table.
1482
1483        Arguments:
1484
1485            rows -- list of Row
1486        """
1487        if rows is None:
1488            rows = []
1489        self.extend(rows)
1490        self._compute_table_cache()
1491        # Update width if necessary
1492        width = self.width
1493        for row in self.traverse():
1494            if row.width > width:
1495                width = row.width
1496        diff = width - self.width
1497        if diff > 0:
1498            self.append_column(Column(repeated=diff))

Append a list of rows at the end of the table.

Arguments:

rows -- list of Row
def append_row( self, row: Row | None = None, clone: bool = True, _repeated: int | None = None) -> Row:
1500    def append_row(
1501        self,
1502        row: Row | None = None,
1503        clone: bool = True,
1504        _repeated: int | None = None,
1505    ) -> Row:
1506        """Append the row at the end of the table. If no row is given, an
1507        empty one is created.
1508
1509        Position start at 0. So cell A4 is on row 3.
1510
1511        Note the columns are automatically created when the first row is
1512        inserted in an empty table. So better insert a filled row.
1513
1514        Arguments:
1515
1516            row -- Row
1517
1518            _repeated -- (optional), repeated value of the row
1519
1520        returns the row, with updated row.y
1521        """
1522        if row is None:
1523            row = Row()
1524            _repeated = 1
1525        elif clone:
1526            row = row.clone
1527        # Appending a repeated row accepted
1528        # Do not insert next to the last row because it could be in a group
1529        self._append(row)
1530        if _repeated is None:
1531            _repeated = row.repeated or 1
1532        self._tmap = insert_map_once(self._tmap, len(self._tmap), _repeated)
1533        row.y = self.height - 1
1534        # Initialize columns
1535        if not self._get_columns():
1536            repeated = row.width
1537            self.insert(Column(repeated=repeated), position=0)
1538            self._compute_table_cache()
1539        # Update width if necessary
1540        self._update_width(row)
1541        return row

Append the row at the end of the table. If no row is given, an empty one is created.

Position start at 0. So cell A4 is on row 3.

Note the columns are automatically created when the first row is inserted in an empty table. So better insert a filled row.

Arguments:

row -- Row

_repeated -- (optional), repeated value of the row

returns the row, with updated row.y

def delete_row(self, y: int | str) -> None:
1543    def delete_row(self, y: int | str) -> None:
1544        """Delete the row at the given "y" position.
1545
1546        Position start at 0. So cell A4 is on row 3.
1547
1548        Arguments:
1549
1550            y -- int or str
1551        """
1552        y = self._translate_y_from_any(y)
1553        # Outside the defined table
1554        if y >= self.height:
1555            return
1556        # Inside the defined table
1557        delete_item_in_vault(y, self, _xpath_row_idx, "_tmap")

Delete the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

Arguments:

y -- int or str
def get_row_values( self, y: int | str, cell_type: str | None = None, complete: bool = True, get_type: bool = False) -> list:
1559    def get_row_values(
1560        self,
1561        y: int | str,
1562        cell_type: str | None = None,
1563        complete: bool = True,
1564        get_type: bool = False,
1565    ) -> list:
1566        """Shortcut to get the list of Python values for the cells of the row
1567        at the given "y" position.
1568
1569        Position start at 0. So cell A4 is on row 3.
1570
1571        Filter by cell_type, with cell_type 'all' will retrieve cells of any
1572        type, aka non empty cells.
1573        If cell_type and complete is True, replace missing values by None.
1574
1575        If get_type is True, returns a tuple (value, ODF type of value)
1576
1577        Arguments:
1578
1579            y -- int, str
1580
1581            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
1582                         'currency', 'percentage' or 'all'
1583
1584            complete -- boolean
1585
1586            get_type -- boolean
1587
1588        Return: list of lists of Python types
1589        """
1590        values = self.get_row(y, clone=False).get_values(
1591            cell_type=cell_type, complete=complete, get_type=get_type
1592        )
1593        # complete row to match column width
1594        if complete:
1595            if get_type:
1596                values.extend([(None, None)] * (self.width - len(values)))
1597            else:
1598                values.extend([None] * (self.width - len(values)))
1599        return values

Shortcut to get the list of Python values for the cells of the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type and complete is True, replace missing values by None.

If get_type is True, returns a tuple (value, ODF type of value)

Arguments:

y -- int, str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: list of lists of Python types

def set_row_values( self, y: int | str, values: list, cell_type: str | None = None, currency: str | None = None, style: str | None = None) -> Row:
1601    def set_row_values(
1602        self,
1603        y: int | str,
1604        values: list,
1605        cell_type: str | None = None,
1606        currency: str | None = None,
1607        style: str | None = None,
1608    ) -> Row:
1609        """Shortcut to set the values of *all* cells of the row at the given
1610        "y" position.
1611
1612        Position start at 0. So cell A4 is on row 3.
1613
1614        Arguments:
1615
1616            y -- int or str
1617
1618            values -- list of Python types
1619
1620            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1621                         'string' or 'time'
1622
1623            currency -- three-letter str
1624
1625            style -- str
1626
1627        returns the row, with updated row.y
1628        """
1629        row = Row()  # needed if clones rows
1630        row.set_values(values, style=style, cell_type=cell_type, currency=currency)
1631        return self.set_row(y, row)  # needed if clones rows

Shortcut to set the values of all cells of the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

Arguments:

y -- int or str

values -- list of Python types

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
             'string' or 'time'

currency -- three-letter str

style -- str

returns the row, with updated row.y

def set_row_cells(self, y: int | str, cells: list | None = None) -> Row:
1633    def set_row_cells(self, y: int | str, cells: list | None = None) -> Row:
1634        """Shortcut to set *all* the cells of the row at the given
1635        "y" position.
1636
1637        Position start at 0. So cell A4 is on row 3.
1638
1639        Arguments:
1640
1641            y -- int or str
1642
1643            cells -- list of Python types
1644
1645            style -- str
1646
1647        returns the row, with updated row.y
1648        """
1649        if cells is None:
1650            cells = []
1651        row = Row()  # needed if clones rows
1652        row.extend_cells(cells)
1653        return self.set_row(y, row)  # needed if clones rows

Shortcut to set all the cells of the row at the given "y" position.

Position start at 0. So cell A4 is on row 3.

Arguments:

y -- int or str

cells -- list of Python types

style -- str

returns the row, with updated row.y

def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool:
1655    def is_row_empty(self, y: int | str, aggressive: bool = False) -> bool:
1656        """Return wether every cell in the row at the given "y" position has
1657        no value or the value evaluates to False (empty string), and no style.
1658
1659        Position start at 0. So cell A4 is on row 3.
1660
1661        If aggressive is True, empty cells with style are considered empty.
1662
1663        Arguments:
1664
1665            y -- int or str
1666
1667            aggressive -- bool
1668        """
1669        return self.get_row(y, clone=False).is_empty(aggressive=aggressive)

Return wether every cell in the row at the given "y" position has no value or the value evaluates to False (empty string), and no style.

Position start at 0. So cell A4 is on row 3.

If aggressive is True, empty cells with style are considered empty.

Arguments:

y -- int or str

aggressive -- bool
def get_cells( self, coord: tuple | list | str | None = None, cell_type: str | None = None, style: str | None = None, content: str | None = None, flat: bool = False) -> list:
1675    def get_cells(
1676        self,
1677        coord: tuple | list | str | None = None,
1678        cell_type: str | None = None,
1679        style: str | None = None,
1680        content: str | None = None,
1681        flat: bool = False,
1682    ) -> list:
1683        """Get the cells matching the criteria. If 'coord' is None,
1684        parse the whole table, else parse the area defined by 'coord'.
1685
1686        Filter by  cell_type = "all"  will retrieve cells of any
1687        type, aka non empty cells.
1688
1689        If flat is True (default is False), the method return a single list
1690        of all the values, else a list of lists of cells.
1691
1692        if cell_type, style and content are None, get_cells() will return
1693        the exact number of cells of the area, including empty cells.
1694
1695        Arguments:
1696
1697            coordinates -- str or tuple of int : coordinates of area
1698
1699            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
1700                         'currency', 'percentage' or 'all'
1701
1702            content -- str regex
1703
1704            style -- str
1705
1706            flat -- boolean
1707
1708        Return: list of tuples
1709        """
1710        if coord:
1711            x, y, z, t = self._translate_table_coordinates(coord)
1712        else:
1713            x = y = z = t = None
1714        if flat:
1715            cells: list[Cell] = []
1716            for row in self.traverse(start=y, end=t):
1717                row_cells = row.get_cells(
1718                    coord=(x, z),
1719                    cell_type=cell_type,
1720                    style=style,
1721                    content=content,
1722                )
1723                cells.extend(row_cells)
1724            return cells
1725        else:
1726            lcells: list[list[Cell]] = []
1727            for row in self.traverse(start=y, end=t):
1728                row_cells = row.get_cells(
1729                    coord=(x, z),
1730                    cell_type=cell_type,
1731                    style=style,
1732                    content=content,
1733                )
1734                lcells.append(row_cells)
1735            return lcells

Get the cells matching the criteria. If 'coord' is None, parse the whole table, else parse the area defined by 'coord'.

Filter by cell_type = "all" will retrieve cells of any type, aka non empty cells.

If flat is True (default is False), the method return a single list of all the values, else a list of lists of cells.

if cell_type, style and content are None, get_cells() will return the exact number of cells of the area, including empty cells.

Arguments:

coordinates -- str or tuple of int : coordinates of area

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

content -- str regex

style -- str

flat -- boolean

Return: list of tuples

def get_cell( self, coord: tuple | list | str, clone: bool = True, keep_repeated: bool = True) -> Cell:
1737    def get_cell(
1738        self,
1739        coord: tuple | list | str,
1740        clone: bool = True,
1741        keep_repeated: bool = True,
1742    ) -> Cell:
1743        """Get the cell at the given coordinates.
1744
1745        They are either a 2-uplet of (x, y) starting from 0, or a
1746        human-readable position like "C4".
1747
1748        A copy is returned, use ``set_cell`` to push it back.
1749
1750        Arguments:
1751
1752            coord -- (int, int) or str
1753
1754        Return: Cell
1755        """
1756        x, y = self._translate_cell_coordinates(coord)
1757        if x is None:
1758            raise ValueError
1759        if y is None:
1760            raise ValueError
1761        # Outside the defined table
1762        if y >= self.height:
1763            cell = Cell()
1764        else:
1765            # Inside the defined table
1766            row = self._get_row2_base(y)
1767            if row is None:
1768                raise ValueError
1769            read_cell = row.get_cell(x, clone=clone)
1770            if read_cell is None:
1771                raise ValueError
1772            cell = read_cell
1773            if not keep_repeated:
1774                repeated = cell.repeated or 1
1775                if repeated >= 2:
1776                    cell.repeated = None
1777        cell.x = x
1778        cell.y = y
1779        return cell

Get the cell at the given coordinates.

They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

A copy is returned, use set_cell to push it back.

Arguments:

coord -- (int, int) or str

Return: Cell

def get_value(self, coord: tuple | list | str, get_type: bool = False) -> Any:
1781    def get_value(
1782        self,
1783        coord: tuple | list | str,
1784        get_type: bool = False,
1785    ) -> Any:
1786        """Shortcut to get the Python value of the cell at the given
1787        coordinates.
1788
1789        If get_type is True, returns the tuples (value, ODF type)
1790
1791        coord is either a 2-uplet of (x, y) starting from 0, or a
1792        human-readable position like "C4". If an Area is given, the upper
1793        left position is used as coord.
1794
1795        Arguments:
1796
1797            coord -- (int, int) or str : coordinate
1798
1799        Return: Python type
1800        """
1801        x, y = self._translate_cell_coordinates(coord)
1802        if x is None:
1803            raise ValueError
1804        if y is None:
1805            raise ValueError
1806        # Outside the defined table
1807        if y >= self.height:
1808            if get_type:
1809                return (None, None)
1810            return None
1811        else:
1812            # Inside the defined table
1813            row = self._get_row2_base(y)
1814            if row is None:
1815                raise ValueError
1816            cell = row._get_cell2_base(x)
1817            if cell is None:
1818                if get_type:
1819                    return (None, None)
1820                return None
1821            return cell.get_value(get_type=get_type)

Shortcut to get the Python value of the cell at the given coordinates.

If get_type is True, returns the tuples (value, ODF type)

coord is either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4". If an Area is given, the upper left position is used as coord.

Arguments:

coord -- (int, int) or str : coordinate

Return: Python type

def set_cell( self, coord: tuple | list | str, cell: Cell | None = None, clone: bool = True) -> Cell:
1823    def set_cell(
1824        self,
1825        coord: tuple | list | str,
1826        cell: Cell | None = None,
1827        clone: bool = True,
1828    ) -> Cell:
1829        """Replace a cell of the table at the given coordinates.
1830
1831        They are either a 2-uplet of (x, y) starting from 0, or a
1832        human-readable position like "C4".
1833
1834        Arguments:
1835
1836            coord -- (int, int) or str : coordinate
1837
1838            cell -- Cell
1839
1840        return the cell, with x and y updated
1841        """
1842        if cell is None:
1843            cell = Cell()
1844            clone = False
1845        x, y = self._translate_cell_coordinates(coord)
1846        if x is None:
1847            raise ValueError
1848        if y is None:
1849            raise ValueError
1850        cell.x = x
1851        cell.y = y
1852        if y >= self.height:
1853            row = Row()
1854            cell_back = row.set_cell(x, cell, clone=clone)
1855            self.set_row(y, row, clone=False)
1856        else:
1857            row_read = self._get_row2_base(y)
1858            if row_read is None:
1859                raise ValueError
1860            row = row_read
1861            row.y = y
1862            repeated = row.repeated or 1
1863            if repeated > 1:
1864                row = row.clone
1865                row.repeated = None
1866                cell_back = row.set_cell(x, cell, clone=clone)
1867                self.set_row(y, row, clone=False)
1868            else:
1869                cell_back = row.set_cell(x, cell, clone=clone)
1870                # Update width if necessary, since we don't use set_row
1871                self._update_width(row)
1872        return cell_back

Replace a cell of the table at the given coordinates.

They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

Arguments:

coord -- (int, int) or str : coordinate

cell -- Cell

return the cell, with x and y updated

def set_cells( self, cells: list[list[Cell]] | list[tuple[Cell]], coord: tuple | list | str | None = None, clone: bool = True) -> None:
1874    def set_cells(
1875        self,
1876        cells: list[list[Cell]] | list[tuple[Cell]],
1877        coord: tuple | list | str | None = None,
1878        clone: bool = True,
1879    ) -> None:
1880        """Set the cells in the table, from the 'coord' position.
1881
1882        'coord' is the coordinate of the upper left cell to be modified by
1883        values. If 'coord' is None, default to the position (0,0) ("A1").
1884        If 'coord' is an area (e.g. "A2:B5"), the upper left position of this
1885        area is used as coordinate.
1886
1887        The table is *not* cleared before the operation, to reset the table
1888        before setting cells, use table.clear().
1889
1890        A list of lists is expected, with as many lists as rows to be set, and
1891        as many cell in each sublist as cells to be setted in the row.
1892
1893        Arguments:
1894
1895            cells -- list of list of cells
1896
1897            coord -- tuple or str
1898
1899            values -- list of lists of python types
1900        """
1901        if coord:
1902            x, y = self._translate_cell_coordinates(coord)
1903        else:
1904            x = y = 0
1905        if y is None:
1906            y = 0
1907        if x is None:
1908            x = 0
1909        y -= 1
1910        for row_cells in cells:
1911            y += 1
1912            if not row_cells:
1913                continue
1914            row = self.get_row(y, clone=True)
1915            repeated = row.repeated or 1
1916            if repeated >= 2:
1917                row.repeated = None
1918            row.set_cells(row_cells, start=x, clone=clone)
1919            self.set_row(y, row, clone=False)
1920            self._update_width(row)

Set the cells in the table, from the 'coord' position.

'coord' is the coordinate of the upper left cell to be modified by values. If 'coord' is None, default to the position (0,0) ("A1"). If 'coord' is an area (e.g. "A2:B5"), the upper left position of this area is used as coordinate.

The table is not cleared before the operation, to reset the table before setting cells, use table.clear().

A list of lists is expected, with as many lists as rows to be set, and as many cell in each sublist as cells to be setted in the row.

Arguments:

cells -- list of list of cells

coord -- tuple or str

values -- list of lists of python types
def set_value( self, coord: tuple | list | str, value: Any, cell_type: str | None = None, currency: str | None = None, style: str | None = None) -> None:
1922    def set_value(
1923        self,
1924        coord: tuple | list | str,
1925        value: Any,
1926        cell_type: str | None = None,
1927        currency: str | None = None,
1928        style: str | None = None,
1929    ) -> None:
1930        """Set the Python value of the cell at the given coordinates.
1931
1932        They are either a 2-uplet of (x, y) starting from 0, or a
1933        human-readable position like "C4".
1934
1935        Arguments:
1936
1937            coord -- (int, int) or str
1938
1939            value -- Python type
1940
1941            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
1942                     'string' or 'time'
1943
1944            currency -- three-letter str
1945
1946            style -- str
1947
1948        """
1949        self.set_cell(
1950            coord,
1951            Cell(value, cell_type=cell_type, currency=currency, style=style),
1952            clone=False,
1953        )

Set the Python value of the cell at the given coordinates.

They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

Arguments:

coord -- (int, int) or str

value -- Python type

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
         'string' or 'time'

currency -- three-letter str

style -- str
def set_cell_image( self, coord: tuple | list | str, image_frame: Frame, doc_type: str | None = None) -> None:
1955    def set_cell_image(
1956        self,
1957        coord: tuple | list | str,
1958        image_frame: Frame,
1959        doc_type: str | None = None,
1960    ) -> None:
1961        """Do all the magic to display an image in the cell at the given
1962        coordinates.
1963
1964        They are either a 2-uplet of (x, y) starting from 0, or a
1965        human-readable position like "C4".
1966
1967        The frame element must contain the expected image position and
1968        dimensions.
1969
1970        DrawImage insertion depends on the document type, so the type must be
1971        provided or the table element must be already attached to a document.
1972
1973        Arguments:
1974
1975            coord -- (int, int) or str
1976
1977            image_frame -- Frame including an image
1978
1979            doc_type -- 'spreadsheet' or 'text'
1980        """
1981        # Test document type
1982        if doc_type is None:
1983            body = self.document_body
1984            if body is None:
1985                raise ValueError("document type not found")
1986            doc_type = {"office:spreadsheet": "spreadsheet", "office:text": "text"}.get(
1987                body.tag
1988            )
1989            if doc_type is None:
1990                raise ValueError("document type not supported for images")
1991        # We need the end address of the image
1992        x, y = self._translate_cell_coordinates(coord)
1993        if x is None:
1994            raise ValueError
1995        if y is None:
1996            raise ValueError
1997        cell = self.get_cell((x, y))
1998        image_frame = image_frame.clone  # type: ignore
1999        # Remove any previous paragraph, frame, etc.
2000        for child in cell.children:
2001            cell.delete(child)
2002        # Now it all depends on the document type
2003        if doc_type == "spreadsheet":
2004            image_frame.anchor_type = "char"
2005            # The frame needs end coordinates
2006            width, height = image_frame.size
2007            image_frame.set_attribute("table:end-x", width)
2008            image_frame.set_attribute("table:end-y", height)
2009            # FIXME what happens when the address changes?
2010            address = f"{self.name}.{digit_to_alpha(x)}{y + 1}"
2011            image_frame.set_attribute("table:end-cell-address", address)
2012            # The frame is directly in the cell
2013            cell.append(image_frame)
2014        elif doc_type == "text":
2015            # The frame must be in a paragraph
2016            cell.set_value("")
2017            paragraph = cell.get_element("text:p")
2018            if paragraph is None:
2019                raise ValueError
2020            paragraph.append(image_frame)
2021        self.set_cell(coord, cell)

Do all the magic to display an image in the cell at the given coordinates.

They are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

The frame element must contain the expected image position and dimensions.

DrawImage insertion depends on the document type, so the type must be provided or the table element must be already attached to a document.

Arguments:

coord -- (int, int) or str

image_frame -- Frame including an image

doc_type -- 'spreadsheet' or 'text'
def insert_cell( self, coord: tuple | list | str, cell: Cell | None = None, clone: bool = True) -> Cell:
2023    def insert_cell(
2024        self,
2025        coord: tuple | list | str,
2026        cell: Cell | None = None,
2027        clone: bool = True,
2028    ) -> Cell:
2029        """Insert the given cell at the given coordinates. If no cell is
2030        given, an empty one is created.
2031
2032        Coordinates are either a 2-uplet of (x, y) starting from 0, or a
2033        human-readable position like "C4".
2034
2035        Cells on the right are shifted. Other rows remain untouched.
2036
2037        Arguments:
2038
2039            coord -- (int, int) or str
2040
2041            cell -- Cell
2042
2043        returns the cell with x and y updated
2044        """
2045        if cell is None:
2046            cell = Cell()
2047            clone = False
2048        if clone:
2049            cell = cell.clone
2050        x, y = self._translate_cell_coordinates(coord)
2051        if x is None:
2052            raise ValueError
2053        if y is None:
2054            raise ValueError
2055        row = self._get_row2(y, clone=True)
2056        row.y = y
2057        row.repeated = None
2058        cell_back = row.insert_cell(x, cell, clone=False)
2059        self.set_row(y, row, clone=False)
2060        # Update width if necessary
2061        self._update_width(row)
2062        return cell_back

Insert the given cell at the given coordinates. If no cell is given, an empty one is created.

Coordinates are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

Cells on the right are shifted. Other rows remain untouched.

Arguments:

coord -- (int, int) or str

cell -- Cell

returns the cell with x and y updated

def append_cell( self, y: int | str, cell: Cell | None = None, clone: bool = True) -> Cell:
2064    def append_cell(
2065        self,
2066        y: int | str,
2067        cell: Cell | None = None,
2068        clone: bool = True,
2069    ) -> Cell:
2070        """Append the given cell at the "y" coordinate. Repeated cells are
2071        accepted. If no cell is given, an empty one is created.
2072
2073        Position start at 0. So cell A4 is on row 3.
2074
2075        Other rows remain untouched.
2076
2077        Arguments:
2078
2079            y -- int or str
2080
2081            cell -- Cell
2082
2083        returns the cell with x and y updated
2084        """
2085        if cell is None:
2086            cell = Cell()
2087            clone = False
2088        if clone:
2089            cell = cell.clone
2090        y = self._translate_y_from_any(y)
2091        row = self._get_row2(y)
2092        row.y = y
2093        cell_back = row.append_cell(cell, clone=False)
2094        self.set_row(y, row)
2095        # Update width if necessary
2096        self._update_width(row)
2097        return cell_back

Append the given cell at the "y" coordinate. Repeated cells are accepted. If no cell is given, an empty one is created.

Position start at 0. So cell A4 is on row 3.

Other rows remain untouched.

Arguments:

y -- int or str

cell -- Cell

returns the cell with x and y updated

def delete_cell(self, coord: tuple | list | str) -> None:
2099    def delete_cell(self, coord: tuple | list | str) -> None:
2100        """Delete the cell at the given coordinates, so that next cells are
2101        shifted to the left.
2102
2103        Coordinates are either a 2-uplet of (x, y) starting from 0, or a
2104        human-readable position like "C4".
2105
2106        Use set_value() for erasing value.
2107
2108        Arguments:
2109
2110            coord -- (int, int) or str
2111        """
2112        x, y = self._translate_cell_coordinates(coord)
2113        if x is None:
2114            raise ValueError
2115        if y is None:
2116            raise ValueError
2117        # Outside the defined table
2118        if y >= self.height:
2119            return
2120        # Inside the defined table
2121        row = self._get_row2_base(y)
2122        if row is None:
2123            raise ValueError
2124        row.delete_cell(x)
2125        # self.set_row(y, row)

Delete the cell at the given coordinates, so that next cells are shifted to the left.

Coordinates are either a 2-uplet of (x, y) starting from 0, or a human-readable position like "C4".

Use set_value() for erasing value.

Arguments:

coord -- (int, int) or str
def traverse_columns( self, start: int | None = None, end: int | None = None) -> collections.abc.Iterator[Column]:
2132    def traverse_columns(  # noqa: C901
2133        self,
2134        start: int | None = None,
2135        end: int | None = None,
2136    ) -> Iterator[Column]:
2137        """Yield as many column elements as expected columns in the table,
2138        i.e. expand repetitions by returning the same column as many times as
2139        necessary.
2140
2141            Arguments:
2142
2143                start -- int
2144
2145                end -- int
2146
2147        Copies are returned, use set_column() to push them back.
2148        """
2149        idx = -1
2150        before = -1
2151        x = 0
2152        if start is None and end is None:
2153            for juska in self._cmap:
2154                idx += 1
2155                if idx in self._indexes["_cmap"]:
2156                    column = self._indexes["_cmap"][idx]
2157                else:
2158                    column = self._get_element_idx2(_xpath_column_idx, idx)
2159                    self._indexes["_cmap"][idx] = column
2160                repeated = juska - before
2161                before = juska
2162                for _i in range(repeated or 1):
2163                    # Return a copy without the now obsolete repetition
2164                    column = column.clone
2165                    column.x = x
2166                    x += 1
2167                    if repeated > 1:
2168                        column.repeated = None
2169                    yield column
2170        else:
2171            if start is None:
2172                start = 0
2173            start = max(0, start)
2174            if end is None:
2175                try:
2176                    end = self._cmap[-1]
2177                except Exception:
2178                    end = -1
2179            start_map = find_odf_idx(self._cmap, start)
2180            if start_map is None:
2181                return
2182            if start_map > 0:
2183                before = self._cmap[start_map - 1]
2184            idx = start_map - 1
2185            before = start - 1
2186            x = start
2187            for juska in self._cmap[start_map:]:
2188                idx += 1
2189                if idx in self._indexes["_cmap"]:
2190                    column = self._indexes["_cmap"][idx]
2191                else:
2192                    column = self._get_element_idx2(_xpath_column_idx, idx)
2193                    self._indexes["_cmap"][idx] = column
2194                repeated = juska - before
2195                before = juska
2196                for _i in range(repeated or 1):
2197                    if x <= end:
2198                        column = column.clone
2199                        column.x = x
2200                        x += 1
2201                        if repeated > 1 or (x == start and start > 0):
2202                            column.repeated = None
2203                        yield column

Yield as many column elements as expected columns in the table, i.e. expand repetitions by returning the same column as many times as necessary.

Arguments:

    start -- int

    end -- int

Copies are returned, use set_column() to push them back.

def get_columns( self, coord: tuple | list | str | None = None, style: str | None = None) -> list[Column]:
2205    def get_columns(
2206        self,
2207        coord: tuple | list | str | None = None,
2208        style: str | None = None,
2209    ) -> list[Column]:
2210        """Get the list of columns matching the criteria. Each result is a
2211        tuple of (x, column).
2212
2213        Arguments:
2214
2215            coord -- str or tuple of int : coordinates of columns
2216
2217            style -- str
2218
2219        Return: list of columns
2220        """
2221        if coord:
2222            x, _y, _z, t = self._translate_column_coordinates(coord)
2223        else:
2224            x = t = None
2225        if not style:
2226            return list(self.traverse_columns(start=x, end=t))
2227        columns = []
2228        for column in self.traverse_columns(start=x, end=t):
2229            if style != column.style:
2230                continue
2231            columns.append(column)
2232        return columns

Get the list of columns matching the criteria. Each result is a tuple of (x, column).

Arguments:

coord -- str or tuple of int : coordinates of columns

style -- str

Return: list of columns

def get_column(self, x: int | str) -> Column:
2249    def get_column(self, x: int | str) -> Column:
2250        """Get the column at the given "x" position.
2251
2252        ODF columns don't contain cells, only style information.
2253
2254        Position start at 0. So cell C4 is on column 2. Alphabetical position
2255        like "C" is accepted.
2256
2257        A copy is returned, use set_column() to push it back.
2258
2259        Arguments:
2260
2261            x -- int or str
2262
2263        Return: Column
2264        """
2265        x = self._translate_x_from_any(x)
2266        column = self._get_column2(x)
2267        if column is None:
2268            raise ValueError
2269        column.x = x
2270        return column

Get the column at the given "x" position.

ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

A copy is returned, use set_column() to push it back.

Arguments:

x -- int or str

Return: Column

def set_column( self, x: int | str, column: Column | None = None) -> Column:
2272    def set_column(
2273        self,
2274        x: int | str,
2275        column: Column | None = None,
2276    ) -> Column:
2277        """Replace the column at the given "x" position.
2278
2279        ODF columns don't contain cells, only style information.
2280
2281        Position start at 0. So cell C4 is on column 2. Alphabetical position
2282        like "C" is accepted.
2283
2284        Arguments:
2285
2286            x -- int or str
2287
2288            column -- Column
2289        """
2290        x = self._translate_x_from_any(x)
2291        if column is None:
2292            column = Column()
2293            repeated = 1
2294        else:
2295            repeated = column.repeated or 1
2296        column.x = x
2297        # Outside the defined table ?
2298        diff = x - self.width
2299        if diff == 0:
2300            column_back = self.append_column(column, _repeated=repeated)
2301        elif diff > 0:
2302            self.append_column(Column(repeated=diff), _repeated=diff)
2303            column_back = self.append_column(column, _repeated=repeated)
2304        else:
2305            # Inside the defined table
2306            column_back = set_item_in_vault(  # type: ignore
2307                x, column, self, _xpath_column_idx, "_cmap"
2308            )
2309        return column_back

Replace the column at the given "x" position.

ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Arguments:

x -- int or str

column -- Column
def insert_column( self, x: int | str, column: Column | None = None) -> Column:
2311    def insert_column(
2312        self,
2313        x: int | str,
2314        column: Column | None = None,
2315    ) -> Column:
2316        """Insert the column before the given "x" position. If no column is
2317        given, an empty one is created.
2318
2319        ODF columns don't contain cells, only style information.
2320
2321        Position start at 0. So cell C4 is on column 2. Alphabetical position
2322        like "C" is accepted.
2323
2324        Arguments:
2325
2326            x -- int or str
2327
2328            column -- Column
2329        """
2330        if column is None:
2331            column = Column()
2332        x = self._translate_x_from_any(x)
2333        diff = x - self.width
2334        if diff < 0:
2335            column_back = insert_item_in_vault(
2336                x, column, self, _xpath_column_idx, "_cmap"
2337            )
2338        elif diff == 0:
2339            column_back = self.append_column(column.clone)
2340        else:
2341            self.append_column(Column(repeated=diff), _repeated=diff)
2342            column_back = self.append_column(column.clone)
2343        column_back.x = x  # type: ignore
2344        # Repetitions are accepted
2345        repeated = column.repeated or 1
2346        # Update width on every row
2347        for row in self._get_rows():
2348            if row.width > x:
2349                row.insert_cell(x, Cell(repeated=repeated))
2350            # Shorter rows don't need insert
2351            # Longer rows shouldn't exist!
2352        return column_back  # type: ignore

Insert the column before the given "x" position. If no column is given, an empty one is created.

ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Arguments:

x -- int or str

column -- Column
def append_column( self, column: Column | None = None, _repeated: int | None = None) -> Column:
2354    def append_column(
2355        self,
2356        column: Column | None = None,
2357        _repeated: int | None = None,
2358    ) -> Column:
2359        """Append the column at the end of the table. If no column is given,
2360        an empty one is created.
2361
2362        ODF columns don't contain cells, only style information.
2363
2364        Position start at 0. So cell C4 is on column 2. Alphabetical position
2365        like "C" is accepted.
2366
2367        Arguments:
2368
2369            column -- Column
2370        """
2371        if column is None:
2372            column = Column()
2373        else:
2374            column = column.clone
2375        if not self._cmap:
2376            position = 0
2377        else:
2378            odf_idx = len(self._cmap) - 1
2379            last_column = self._get_element_idx2(_xpath_column_idx, odf_idx)
2380            if last_column is None:
2381                raise ValueError
2382            position = self.index(last_column) + 1
2383        column.x = self.width
2384        self.insert(column, position=position)
2385        # Repetitions are accepted
2386        if _repeated is None:
2387            _repeated = column.repeated or 1
2388        self._cmap = insert_map_once(self._cmap, len(self._cmap), _repeated)
2389        # No need to update row widths
2390        return column

Append the column at the end of the table. If no column is given, an empty one is created.

ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Arguments:

column -- Column
def delete_column(self, x: int | str) -> None:
2392    def delete_column(self, x: int | str) -> None:
2393        """Delete the column at the given position. ODF columns don't contain
2394        cells, only style information.
2395
2396        Position start at 0. So cell C4 is on column 2. Alphabetical position
2397        like "C" is accepted.
2398
2399        Arguments:
2400
2401            x -- int or str
2402        """
2403        x = self._translate_x_from_any(x)
2404        # Outside the defined table
2405        if x >= self.width:
2406            return
2407        # Inside the defined table
2408        delete_item_in_vault(x, self, _xpath_column_idx, "_cmap")
2409        # Update width
2410        width = self.width
2411        for row in self._get_rows():
2412            if row.width >= width:
2413                row.delete_cell(x)

Delete the column at the given position. ODF columns don't contain cells, only style information.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Arguments:

x -- int or str
def get_column_cells( self, x: int | str, style: str | None = None, content: str | None = None, cell_type: str | None = None, complete: bool = False) -> list[Cell | None]:
2415    def get_column_cells(  # noqa: C901
2416        self,
2417        x: int | str,
2418        style: str | None = None,
2419        content: str | None = None,
2420        cell_type: str | None = None,
2421        complete: bool = False,
2422    ) -> list[Cell | None]:
2423        """Get the list of cells at the given position.
2424
2425        Position start at 0. So cell C4 is on column 2. Alphabetical position
2426        like "C" is accepted.
2427
2428        Filter by cell_type, with cell_type 'all' will retrieve cells of any
2429        type, aka non empty cells.
2430
2431        If complete is True, replace missing values by None.
2432
2433        Arguments:
2434
2435            x -- int or str
2436
2437            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
2438                         'currency', 'percentage' or 'all'
2439
2440            content -- str regex
2441
2442            style -- str
2443
2444            complete -- boolean
2445
2446        Return: list of Cell
2447        """
2448        x = self._translate_x_from_any(x)
2449        if cell_type:
2450            cell_type = cell_type.lower().strip()
2451        cells: list[Cell | None] = []
2452        if not style and not content and not cell_type:
2453            for row in self.traverse():
2454                cells.append(row.get_cell(x, clone=True))
2455            return cells
2456        for row in self.traverse():
2457            cell = row.get_cell(x, clone=True)
2458            if cell is None:
2459                raise ValueError
2460            # Filter the cells by cell_type
2461            if cell_type:
2462                ctype = cell.type
2463                if not ctype or not (ctype == cell_type or cell_type == "all"):
2464                    if complete:
2465                        cells.append(None)
2466                    continue
2467            # Filter the cells with the regex
2468            if content and not cell.match(content):
2469                if complete:
2470                    cells.append(None)
2471                continue
2472            # Filter the cells with the style
2473            if style and style != cell.style:
2474                if complete:
2475                    cells.append(None)
2476                continue
2477            cells.append(cell)
2478        return cells

Get the list of cells at the given position.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells.

If complete is True, replace missing values by None.

Arguments:

x -- int or str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

content -- str regex

style -- str

complete -- boolean

Return: list of Cell

def get_column_values( self, x: int | str, cell_type: str | None = None, complete: bool = True, get_type: bool = False) -> list[typing.Any]:
2480    def get_column_values(
2481        self,
2482        x: int | str,
2483        cell_type: str | None = None,
2484        complete: bool = True,
2485        get_type: bool = False,
2486    ) -> list[Any]:
2487        """Shortcut to get the list of Python values for the cells at the
2488        given position.
2489
2490        Position start at 0. So cell C4 is on column 2. Alphabetical position
2491        like "C" is accepted.
2492
2493        Filter by cell_type, with cell_type 'all' will retrieve cells of any
2494        type, aka non empty cells.
2495        If cell_type and complete is True, replace missing values by None.
2496
2497        If get_type is True, returns a tuple (value, ODF type of value)
2498
2499        Arguments:
2500
2501            x -- int or str
2502
2503            cell_type -- 'boolean', 'float', 'date', 'string', 'time',
2504                         'currency', 'percentage' or 'all'
2505
2506            complete -- boolean
2507
2508            get_type -- boolean
2509
2510        Return: list of Python types
2511        """
2512        cells = self.get_column_cells(
2513            x, style=None, content=None, cell_type=cell_type, complete=complete
2514        )
2515        values: list[Any] = []
2516        for cell in cells:
2517            if cell is None:
2518                if complete:
2519                    if get_type:
2520                        values.append((None, None))
2521                    else:
2522                        values.append(None)
2523                continue
2524            if cell_type:
2525                ctype = cell.type
2526                if not ctype or not (ctype == cell_type or cell_type == "all"):
2527                    if complete:
2528                        if get_type:
2529                            values.append((None, None))
2530                        else:
2531                            values.append(None)
2532                    continue
2533            values.append(cell.get_value(get_type=get_type))
2534        return values

Shortcut to get the list of Python values for the cells at the given position.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

Filter by cell_type, with cell_type 'all' will retrieve cells of any type, aka non empty cells. If cell_type and complete is True, replace missing values by None.

If get_type is True, returns a tuple (value, ODF type of value)

Arguments:

x -- int or str

cell_type -- 'boolean', 'float', 'date', 'string', 'time',
             'currency', 'percentage' or 'all'

complete -- boolean

get_type -- boolean

Return: list of Python types

def set_column_cells(self, x: int | str, cells: list[Cell]) -> None:
2536    def set_column_cells(self, x: int | str, cells: list[Cell]) -> None:
2537        """Shortcut to set the list of cells at the given position.
2538
2539        Position start at 0. So cell C4 is on column 2. Alphabetical position
2540        like "C" is accepted.
2541
2542        The list must have the same length than the table height.
2543
2544        Arguments:
2545
2546            x -- int or str
2547
2548            cells -- list of Cell
2549        """
2550        height = self.height
2551        if len(cells) != height:
2552            raise ValueError(f"col mismatch: {height} cells expected")
2553        cells_iterator = iter(cells)
2554        for y, row in enumerate(self.traverse()):
2555            row.set_cell(x, next(cells_iterator))
2556            self.set_row(y, row)

Shortcut to set the list of cells at the given position.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

The list must have the same length than the table height.

Arguments:

x -- int or str

cells -- list of Cell
def set_column_values( self, x: int | str, values: list, cell_type: str | None = None, currency: str | None = None, style: str | None = None) -> None:
2558    def set_column_values(
2559        self,
2560        x: int | str,
2561        values: list,
2562        cell_type: str | None = None,
2563        currency: str | None = None,
2564        style: str | None = None,
2565    ) -> None:
2566        """Shortcut to set the list of Python values of cells at the given
2567        position.
2568
2569        Position start at 0. So cell C4 is on column 2. Alphabetical position
2570        like "C" is accepted.
2571
2572        The list must have the same length than the table height.
2573
2574        Arguments:
2575
2576            x -- int or str
2577
2578            values -- list of Python types
2579
2580            cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
2581                         'string' or 'time'
2582
2583            currency -- three-letter str
2584
2585            style -- str
2586        """
2587        cells = [
2588            Cell(value, cell_type=cell_type, currency=currency, style=style)
2589            for value in values
2590        ]
2591        self.set_column_cells(x, cells)

Shortcut to set the list of Python values of cells at the given position.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

The list must have the same length than the table height.

Arguments:

x -- int or str

values -- list of Python types

cell_type -- 'boolean', 'currency', 'date', 'float', 'percentage',
             'string' or 'time'

currency -- three-letter str

style -- str
def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool:
2593    def is_column_empty(self, x: int | str, aggressive: bool = False) -> bool:
2594        """Return wether every cell in the column at "x" position has no value
2595        or the value evaluates to False (empty string), and no style.
2596
2597        Position start at 0. So cell C4 is on column 2. Alphabetical position
2598        like "C" is accepted.
2599
2600        If aggressive is True, empty cells with style are considered empty.
2601
2602        Return: bool
2603        """
2604        for cell in self.get_column_cells(x):
2605            if cell is None:
2606                continue
2607            if not cell.is_empty(aggressive=aggressive):
2608                return False
2609        return True

Return wether every cell in the column at "x" position has no value or the value evaluates to False (empty string), and no style.

Position start at 0. So cell C4 is on column 2. Alphabetical position like "C" is accepted.

If aggressive is True, empty cells with style are considered empty.

Return: bool

def get_named_ranges( self, table_name: str | list[str] | None = None) -> list[NamedRange]:
2613    def get_named_ranges(  # type: ignore
2614        self,
2615        table_name: str | list[str] | None = None,
2616    ) -> list[NamedRange]:
2617        """Returns the list of available Name Ranges of the spreadsheet. If
2618        table_name is provided, limits the search to these tables.
2619        Beware : named ranges are stored at the body level, thus do not call
2620        this method on a cloned table.
2621
2622        Arguments:
2623
2624            table_names -- str or list of str, names of tables
2625
2626        Return : list of table_range
2627        """
2628        body = self.document_body
2629        if not body:
2630            return []
2631        all_named_ranges = body.get_named_ranges()
2632        if not table_name:
2633            return all_named_ranges  # type:ignore
2634        filter_ = []
2635        if isinstance(table_name, str):
2636            filter_.append(table_name)
2637        elif isiterable(table_name):
2638            filter_.extend(table_name)
2639        else:
2640            raise ValueError(
2641                f"table_name must be string or Iterable, not {type(table_name)}"
2642            )
2643        return [
2644            nr for nr in all_named_ranges if nr.table_name in filter_  # type:ignore
2645        ]

Returns the list of available Name Ranges of the spreadsheet. If table_name is provided, limits the search to these tables. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.

Arguments:

table_names -- str or list of str, names of tables

Return : list of table_range

def get_named_range(self, name: str) -> NamedRange:
2647    def get_named_range(self, name: str) -> NamedRange:
2648        """Returns the Name Ranges of the specified name. If
2649        table_name is provided, limits the search to these tables.
2650        Beware : named ranges are stored at the body level, thus do not call
2651        this method on a cloned table.
2652
2653        Arguments:
2654
2655            name -- str, name of the named range object
2656
2657        Return : NamedRange
2658        """
2659        body = self.document_body
2660        if not body:
2661            raise ValueError("Table is not inside a document")
2662        return body.get_named_range(name)  # type: ignore

Returns the Name Ranges of the specified name. If table_name is provided, limits the search to these tables. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.

Arguments:

name -- str, name of the named range object

Return : NamedRange

def set_named_range( self, name: str, crange: str | tuple | list, table_name: str | None = None, usage: str | None = None) -> None:
2664    def set_named_range(
2665        self,
2666        name: str,
2667        crange: str | tuple | list,
2668        table_name: str | None = None,
2669        usage: str | None = None,
2670    ) -> None:
2671        """Create a Named Range element and insert it in the document.
2672        Beware : named ranges are stored at the body level, thus do not call
2673        this method on a cloned table.
2674
2675        Arguments:
2676
2677            name -- str, name of the named range
2678
2679            crange -- str or tuple of int, cell or area coordinate
2680
2681            table_name -- str, name of the table
2682
2683            uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
2684        """
2685        body = self.document_body
2686        if not body:
2687            raise ValueError("Table is not inside a document")
2688        if not name:
2689            raise ValueError("Name required.")
2690        if table_name is None:
2691            table_name = self.name
2692        named_range = NamedRange(name, crange, table_name, usage)
2693        body.append_named_range(named_range)

Create a Named Range element and insert it in the document. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.

Arguments:

name -- str, name of the named range

crange -- str or tuple of int, cell or area coordinate

table_name -- str, name of the table

uage -- None or 'print-range', 'filter', 'repeat-column', 'repeat-row'
def delete_named_range(self, name: str) -> None:
2695    def delete_named_range(self, name: str) -> None:
2696        """Delete the Named Range of specified name from the spreadsheet.
2697        Beware : named ranges are stored at the body level, thus do not call
2698        this method on a cloned table.
2699
2700        Arguments:
2701
2702            name -- str
2703        """
2704        name = name.strip()
2705        if not name:
2706            raise ValueError("Name required.")
2707        body = self.document_body
2708        if not body:
2709            raise ValueError("Table is not inside a document.")
2710        body.delete_named_range(name)

Delete the Named Range of specified name from the spreadsheet. Beware : named ranges are stored at the body level, thus do not call this method on a cloned table.

Arguments:

name -- str
def set_span(self, area: str | tuple | list, merge: bool = False) -> bool:
2716    def set_span(  # noqa: C901
2717        self,
2718        area: str | tuple | list,
2719        merge: bool = False,
2720    ) -> bool:
2721        """Create a Cell Span : span the first cell of the area on several
2722        columns and/or rows.
2723        If merge is True, replace text of the cell by the concatenation of
2724        existing text in covered cells.
2725        Beware : if merge is True, old text is changed, if merge is False
2726        (the default), old text in coverd cells is still present but not
2727        displayed by most GUI.
2728
2729        If the area defines only one cell, the set span will do nothing.
2730        It is not allowed to apply set span to an area whose one cell already
2731        belongs to previous cell span.
2732
2733        Area can be either one cell (like 'A1') or an area ('A1:B2'). It can
2734        be provided as an alpha numeric value like "A1:B2' or a tuple like
2735        (0, 0, 1, 1) or (0, 0).
2736
2737        Arguments:
2738
2739            area -- str or tuple of int, cell or area coordinate
2740
2741            merge -- boolean
2742        """
2743        # get area
2744        digits = convert_coordinates(area)
2745        if len(digits) == 4:
2746            x, y, z, t = digits
2747        else:
2748            x, y = digits
2749            z, t = digits
2750        start = x, y
2751        end = z, t
2752        if start == end:
2753            # one cell : do nothing
2754            return False
2755        if x is None:
2756            raise ValueError
2757        if y is None:
2758            raise ValueError
2759        if z is None:
2760            raise ValueError
2761        if t is None:
2762            raise ValueError
2763        # check for previous span
2764        good = True
2765        # Check boundaries and empty cells : need to crate non existent cells
2766        # so don't use get_cells directly, but get_cell
2767        cells = []
2768        for yy in range(y, t + 1):
2769            row_cells = []
2770            for xx in range(x, z + 1):
2771                row_cells.append(
2772                    self.get_cell((xx, yy), clone=True, keep_repeated=False)
2773                )
2774            cells.append(row_cells)
2775        for row in cells:
2776            for cell in row:
2777                if cell.is_spanned():
2778                    good = False
2779                    break
2780            if not good:
2781                break
2782        if not good:
2783            return False
2784        # Check boundaries
2785        # if z >= self.width or t >= self.height:
2786        #    self.set_cell(coord = end)
2787        #    print area, z, t
2788        #    cells = self.get_cells((x, y, z, t))
2789        #    print cells
2790        # do it:
2791        if merge:
2792            val_list = []
2793            for row in cells:
2794                for cell in row:
2795                    if cell.is_empty(aggressive=True):
2796                        continue
2797                    val = cell.get_value()
2798                    if val is not None:
2799                        if isinstance(val, str):
2800                            val.strip()
2801                        if val != "":
2802                            val_list.append(val)
2803                        cell.clear()
2804            if val_list:
2805                if len(val_list) == 1:
2806                    cells[0][0].set_value(val_list[0])
2807                else:
2808                    value = " ".join([str(v) for v in val_list if v])
2809                    cells[0][0].set_value(value)
2810        cols = z - x + 1
2811        cells[0][0].set_attribute("table:number-columns-spanned", str(cols))
2812        rows = t - y + 1
2813        cells[0][0].set_attribute("table:number-rows-spanned", str(rows))
2814        for cell in cells[0][1:]:
2815            cell.tag = "table:covered-table-cell"
2816        for row in cells[1:]:
2817            for cell in row:
2818                cell.tag = "table:covered-table-cell"
2819        # replace cells in table
2820        self.set_cells(cells, coord=start, clone=False)
2821        return True

Create a Cell Span : span the first cell of the area on several columns and/or rows. If merge is True, replace text of the cell by the concatenation of existing text in covered cells. Beware : if merge is True, old text is changed, if merge is False (the default), old text in coverd cells is still present but not displayed by most GUI.

If the area defines only one cell, the set span will do nothing. It is not allowed to apply set span to an area whose one cell already belongs to previous cell span.

Area can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0).

Arguments:

area -- str or tuple of int, cell or area coordinate

merge -- boolean
def del_span(self, area: str | tuple | list) -> bool:
2823    def del_span(self, area: str | tuple | list) -> bool:
2824        """Delete a Cell Span. 'area' is the cell coordiante of the upper left
2825        cell of the spanned area.
2826
2827        Area can be either one cell (like 'A1') or an area ('A1:B2'). It can
2828        be provided as an alpha numeric value like "A1:B2' or a tuple like
2829        (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell
2830        is used.
2831
2832        Arguments:
2833
2834            area -- str or tuple of int, cell or area coordinate
2835        """
2836        # get area
2837        digits = convert_coordinates(area)
2838        if len(digits) == 4:
2839            x, y, _z, _t = digits
2840        else:
2841            x, y = digits
2842        if x is None:
2843            raise ValueError
2844        if y is None:
2845            raise ValueError
2846        start = x, y
2847        # check for previous span
2848        cell0 = self.get_cell(start)
2849        nb_cols = cell0.get_attribute_integer("table:number-columns-spanned")
2850        if nb_cols is None:
2851            return False
2852        nb_rows = cell0.get_attribute_integer("table:number-rows-spanned")
2853        if nb_rows is None:
2854            return False
2855        z = x + nb_cols - 1
2856        t = y + nb_rows - 1
2857        cells = self.get_cells((x, y, z, t))
2858        cells[0][0].del_attribute("table:number-columns-spanned")
2859        cells[0][0].del_attribute("table:number-rows-spanned")
2860        for cell in cells[0][1:]:
2861            cell.tag = "table:table-cell"
2862        for row in cells[1:]:
2863            for cell in row:
2864                cell.tag = "table:table-cell"
2865        # replace cells in table
2866        self.set_cells(cells, coord=start, clone=False)
2867        return True

Delete a Cell Span. 'area' is the cell coordiante of the upper left cell of the spanned area.

Area can be either one cell (like 'A1') or an area ('A1:B2'). It can be provided as an alpha numeric value like "A1:B2' or a tuple like (0, 0, 1, 1) or (0, 0). If an area is provided, the upper left cell is used.

Arguments:

area -- str or tuple of int, cell or area coordinate
def to_csv( self, path_or_file: str | pathlib.Path | None = None, dialect: str = 'excel') -> Any:
2871    def to_csv(
2872        self,
2873        path_or_file: str | Path | None = None,
2874        dialect: str = "excel",
2875    ) -> Any:
2876        """Write the table as CSV in the file.
2877
2878        If the file is a string, it is opened as a local path. Else an
2879        opened file-like is expected.
2880
2881        Arguments:
2882
2883            path_or_file -- str or file-like
2884
2885            dialect -- str, python csv.dialect, can be 'excel', 'unix'...
2886        """
2887
2888        def write_content(csv_writer: object) -> None:
2889            for values in self.iter_values():
2890                line = []
2891                for value in values:
2892                    if value is None:
2893                        value = ""
2894                    if isinstance(value, str):
2895                        value = value.strip()
2896                    line.append(value)
2897                csv_writer.writerow(line)  # type: ignore
2898
2899        out = StringIO(newline="")
2900        csv_writer = csv.writer(out, dialect=dialect)
2901        write_content(csv_writer)
2902        if path_or_file is None:
2903            return out.getvalue()
2904        path = Path(path_or_file)
2905        path.write_text(out.getvalue())
2906        return None

Write the table as CSV in the file.

If the file is a string, it is opened as a local path. Else an opened file-like is expected.

Arguments:

path_or_file -- str or file-like

dialect -- str, python csv.dialect, can be 'excel', 'unix'...
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
get_between
insert
extend
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
append_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class Text(builtins.str):
275class Text(str):
276    """Representation of an XML text node. Created to hide the specifics of
277    lxml in searching text nodes using XPath.
278
279    Constructed like any str object but only accepts lxml text objects.
280    """
281
282    # There's some black magic in inheriting from str
283    def __init__(
284        self,
285        text_result: _ElementUnicodeResult | _ElementStringResult,
286    ) -> None:
287        self.__parent = text_result.getparent()
288        self.__is_text = text_result.is_text
289        self.__is_tail = text_result.is_tail
290
291    @property
292    def parent(self) -> Element | None:
293        parent = self.__parent
294        # XXX happens just because of the unit test
295        if parent is None:
296            return None
297        return Element.from_tag(tag_or_elem=parent)
298
299    def is_text(self) -> bool:
300        return self.__is_text
301
302    def is_tail(self) -> bool:
303        return self.__is_tail

Representation of an XML text node. Created to hide the specifics of lxml in searching text nodes using XPath.

Constructed like any str object but only accepts lxml text objects.

Text( text_result: lxml.etree._ElementUnicodeResult | lxml.etree._ElementStringResult)
283    def __init__(
284        self,
285        text_result: _ElementUnicodeResult | _ElementStringResult,
286    ) -> None:
287        self.__parent = text_result.getparent()
288        self.__is_text = text_result.is_text
289        self.__is_tail = text_result.is_tail
parent: Element | None
291    @property
292    def parent(self) -> Element | None:
293        parent = self.__parent
294        # XXX happens just because of the unit test
295        if parent is None:
296            return None
297        return Element.from_tag(tag_or_elem=parent)
def is_text(self) -> bool:
299    def is_text(self) -> bool:
300        return self.__is_text
def is_tail(self) -> bool:
302    def is_tail(self) -> bool:
303        return self.__is_tail
Inherited Members
builtins.str
encode
replace
split
rsplit
join
capitalize
casefold
title
center
count
expandtabs
find
partition
index
ljust
lower
lstrip
rfind
rindex
rjust
rstrip
rpartition
splitlines
strip
swapcase
translate
upper
startswith
endswith
removeprefix
removesuffix
isascii
islower
isupper
istitle
isspace
isdecimal
isdigit
isnumeric
isalpha
isalnum
isidentifier
isprintable
zfill
format
format_map
maketrans
class TextChange(odfdo.Element):
508class TextChange(Element):
509    """The TextChange "text:change" element marks a position in an empty
510    region where text has been deleted.
511    """
512
513    _tag = "text:change"
514
515    def get_id(self) -> str | None:
516        return self.get_attribute_string("text:change-id")
517
518    def set_id(self, idx: str) -> None:
519        self.set_attribute("text:change-id", idx)
520
521    def _get_tracked_changes(self) -> Element | None:
522        body = self.document_body
523        if not body:
524            raise ValueError
525        return body.get_tracked_changes()
526
527    def get_changed_region(
528        self,
529        tracked_changes: Element | None = None,
530    ) -> Element | None:
531        if not tracked_changes:
532            tracked_changes = self._get_tracked_changes()
533        idx = self.get_id()
534        return tracked_changes.get_changed_region(text_id=idx)  # type: ignore
535
536    def get_change_info(
537        self,
538        tracked_changes: Element | None = None,
539    ) -> Element | None:
540        changed_region = self.get_changed_region(tracked_changes=tracked_changes)
541        if not changed_region:
542            return None
543        return changed_region.get_change_info()  # type: ignore
544
545    def get_change_element(
546        self,
547        tracked_changes: Element | None = None,
548    ) -> Element | None:
549        changed_region = self.get_changed_region(tracked_changes=tracked_changes)
550        if not changed_region:
551            return None
552        return changed_region.get_change_element()  # type: ignore
553
554    def get_deleted(
555        self,
556        tracked_changes: Element | None = None,
557        as_text: bool = False,
558        no_header: bool = False,
559        clean: bool = True,
560    ) -> Element | None:
561        """Shortcut to get the deleted informations stored in the
562        TextDeletion stored in the tracked changes.
563
564        Return: Paragraph (or None)."
565        """
566        changed = self.get_change_element(tracked_changes=tracked_changes)
567        if not changed:
568            return None
569        return changed.get_deleted(  # type: ignore
570            as_text=as_text,
571            no_header=no_header,
572            clean=clean,
573        )
574
575    def get_inserted(
576        self,
577        as_text: bool = False,
578        no_header: bool = False,
579        clean: bool = True,
580    ) -> str | Element | list[Element] | None:
581        """Return None."""
582        return None
583
584    def get_start(self) -> TextChangeStart | None:
585        """Return None."""
586        return None
587
588    def get_end(self) -> TextChangeEnd | None:
589        """Return None."""
590        return None

The TextChange "text:change" element marks a position in an empty region where text has been deleted.

def get_id(self) -> str | None:
515    def get_id(self) -> str | None:
516        return self.get_attribute_string("text:change-id")
def set_id(self, idx: str) -> None:
518    def set_id(self, idx: str) -> None:
519        self.set_attribute("text:change-id", idx)
def get_changed_region( self, tracked_changes: Element | None = None) -> Element | None:
527    def get_changed_region(
528        self,
529        tracked_changes: Element | None = None,
530    ) -> Element | None:
531        if not tracked_changes:
532            tracked_changes = self._get_tracked_changes()
533        idx = self.get_id()
534        return tracked_changes.get_changed_region(text_id=idx)  # type: ignore
def get_change_info( self, tracked_changes: Element | None = None) -> Element | None:
536    def get_change_info(
537        self,
538        tracked_changes: Element | None = None,
539    ) -> Element | None:
540        changed_region = self.get_changed_region(tracked_changes=tracked_changes)
541        if not changed_region:
542            return None
543        return changed_region.get_change_info()  # type: ignore
def get_change_element( self, tracked_changes: Element | None = None) -> Element | None:
545    def get_change_element(
546        self,
547        tracked_changes: Element | None = None,
548    ) -> Element | None:
549        changed_region = self.get_changed_region(tracked_changes=tracked_changes)
550        if not changed_region:
551            return None
552        return changed_region.get_change_element()  # type: ignore
def get_deleted( self, tracked_changes: Element | None = None, as_text: bool = False, no_header: bool = False, clean: bool = True) -> Element | None:
554    def get_deleted(
555        self,
556        tracked_changes: Element | None = None,
557        as_text: bool = False,
558        no_header: bool = False,
559        clean: bool = True,
560    ) -> Element | None:
561        """Shortcut to get the deleted informations stored in the
562        TextDeletion stored in the tracked changes.
563
564        Return: Paragraph (or None)."
565        """
566        changed = self.get_change_element(tracked_changes=tracked_changes)
567        if not changed:
568            return None
569        return changed.get_deleted(  # type: ignore
570            as_text=as_text,
571            no_header=no_header,
572            clean=clean,
573        )

Shortcut to get the deleted informations stored in the TextDeletion stored in the tracked changes.

Return: Paragraph (or None)."

def get_inserted( self, as_text: bool = False, no_header: bool = False, clean: bool = True) -> str | Element | list[Element] | None:
575    def get_inserted(
576        self,
577        as_text: bool = False,
578        no_header: bool = False,
579        clean: bool = True,
580    ) -> str | Element | list[Element] | None:
581        """Return None."""
582        return None

Return None.

def get_start(self) -> TextChangeStart | None:
584    def get_start(self) -> TextChangeStart | None:
585        """Return None."""
586        return None

Return None.

def get_end(self) -> TextChangeEnd | None:
588    def get_end(self) -> TextChangeEnd | None:
589        """Return None."""
590        return None

Return None.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TextChangeEnd(odfdo.TextChange):
593class TextChangeEnd(TextChange):
594    """The TextChangeEnd "text:change-end" element marks the end of a region
595    with content where text has been inserted or the format has been
596    changed.
597    """
598
599    _tag = "text:change-end"
600
601    def get_start(self) -> TextChangeStart | None:
602        """Return the corresponding annotation starting tag or None."""
603        idx = self.get_id()
604        parent = self.parent
605        if parent is None:
606            raise ValueError("Can not find end tag: no parent available.")
607        body = self.document_body
608        if not body:
609            body = self.root
610        return body.get_text_change_start(idx=idx)  # type: ignore
611
612    def get_end(self) -> TextChangeEnd | None:
613        """Return self."""
614        return self
615
616    def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None:
617        """Return None."""
618        return None
619
620    def get_inserted(
621        self,
622        as_text: bool = False,
623        no_header: bool = False,
624        clean: bool = True,
625    ) -> str | Element | list[Element] | None:
626        """Return the content between text:change-start and text:change-end.
627
628        If no content exists (deletion tag), returns None (or '' if text flag
629        is True).
630        If as_text is True: returns the text content.
631        If clean is True: suppress unwanted tags (deletions marks, ...)
632        If no_header is True: existing text:h are changed in text:p
633        By default: returns a list of Element, cleaned and with headers
634
635        Arguments:
636
637            as_text -- boolean
638
639            clean -- boolean
640
641            no_header -- boolean
642
643        Return: list or Element or text
644        """
645
646        # idx = self.get_id()
647        start = self.get_start()
648        end = self.get_end()
649        if end is None or start is None:
650            if as_text:
651                return ""
652            return None
653        body = self.document_body
654        if not body:
655            body = self.root
656        return body.get_between(
657            start, end, as_text=as_text, no_header=no_header, clean=clean
658        )

The TextChangeEnd "text:change-end" element marks the end of a region with content where text has been inserted or the format has been changed.

def get_start(self) -> TextChangeStart | None:
601    def get_start(self) -> TextChangeStart | None:
602        """Return the corresponding annotation starting tag or None."""
603        idx = self.get_id()
604        parent = self.parent
605        if parent is None:
606            raise ValueError("Can not find end tag: no parent available.")
607        body = self.document_body
608        if not body:
609            body = self.root
610        return body.get_text_change_start(idx=idx)  # type: ignore

Return the corresponding annotation starting tag or None.

def get_end(self) -> TextChangeEnd | None:
612    def get_end(self) -> TextChangeEnd | None:
613        """Return self."""
614        return self

Return self.

def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None:
616    def get_deleted(self, *args: Any, **kwargs: Any) -> Element | None:
617        """Return None."""
618        return None

Return None.

def get_inserted( self, as_text: bool = False, no_header: bool = False, clean: bool = True) -> str | Element | list[Element] | None:
620    def get_inserted(
621        self,
622        as_text: bool = False,
623        no_header: bool = False,
624        clean: bool = True,
625    ) -> str | Element | list[Element] | None:
626        """Return the content between text:change-start and text:change-end.
627
628        If no content exists (deletion tag), returns None (or '' if text flag
629        is True).
630        If as_text is True: returns the text content.
631        If clean is True: suppress unwanted tags (deletions marks, ...)
632        If no_header is True: existing text:h are changed in text:p
633        By default: returns a list of Element, cleaned and with headers
634
635        Arguments:
636
637            as_text -- boolean
638
639            clean -- boolean
640
641            no_header -- boolean
642
643        Return: list or Element or text
644        """
645
646        # idx = self.get_id()
647        start = self.get_start()
648        end = self.get_end()
649        if end is None or start is None:
650            if as_text:
651                return ""
652            return None
653        body = self.document_body
654        if not body:
655            body = self.root
656        return body.get_between(
657            start, end, as_text=as_text, no_header=no_header, clean=clean
658        )

Return the content between text:change-start and text:change-end.

If no content exists (deletion tag), returns None (or '' if text flag is True). If as_text is True: returns the text content. If clean is True: suppress unwanted tags (deletions marks, ...) If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and with headers

Arguments:

as_text -- boolean

clean -- boolean

no_header -- boolean

Return: list or Element or text

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
TextChange
get_id
set_id
get_changed_region
get_change_info
get_change_element
class TextChangeStart(odfdo.TextChangeEnd):
661class TextChangeStart(TextChangeEnd):
662    """The TextChangeStart "text:change-start" element marks the start of a
663    region with content where text has been inserted or the format has
664    been changed.
665    """
666
667    _tag = "text:change-start"
668
669    def get_start(self) -> TextChangeStart:
670        """Return self."""
671        return self
672
673    def get_end(self) -> TextChangeEnd:
674        """Return the corresponding change-end tag or None."""
675        idx = self.get_id()
676        parent = self.parent
677        if parent is None:
678            raise ValueError("Can not find end tag: no parent available.")
679        body = self.document_body
680        if not body:
681            body = self.root
682        return body.get_text_change_end(idx=idx)  # type: ignore
683
684    def delete(
685        self,
686        child: Element | None = None,
687        keep_tail: bool = True,
688    ) -> None:
689        """Delete the given element from the XML tree. If no element is given,
690        "self" is deleted. The XML library may allow to continue to use an
691        element now "orphan" as long as you have a reference to it.
692
693        For TextChangeStart : delete also the end tag if exists.
694
695        Arguments:
696
697            child -- Element
698
699            keep_tail -- boolean (default to True), True for most usages.
700        """
701        if child is not None:  # act like normal delete
702            return super().delete(child, keep_tail)
703        idx = self.get_id()
704        parent = self.parent
705        if parent is None:
706            raise ValueError("cannot delete the root element")
707        body = self.document_body
708        if not body:
709            body = parent
710        end = body.get_text_change_end(idx=idx)
711        if end:
712            end.delete()
713        # act like normal delete
714        super().delete()

The TextChangeStart "text:change-start" element marks the start of a region with content where text has been inserted or the format has been changed.

def get_start(self) -> TextChangeStart:
669    def get_start(self) -> TextChangeStart:
670        """Return self."""
671        return self

Return self.

def get_end(self) -> TextChangeEnd:
673    def get_end(self) -> TextChangeEnd:
674        """Return the corresponding change-end tag or None."""
675        idx = self.get_id()
676        parent = self.parent
677        if parent is None:
678            raise ValueError("Can not find end tag: no parent available.")
679        body = self.document_body
680        if not body:
681            body = self.root
682        return body.get_text_change_end(idx=idx)  # type: ignore

Return the corresponding change-end tag or None.

def delete( self, child: Element | None = None, keep_tail: bool = True) -> None:
684    def delete(
685        self,
686        child: Element | None = None,
687        keep_tail: bool = True,
688    ) -> None:
689        """Delete the given element from the XML tree. If no element is given,
690        "self" is deleted. The XML library may allow to continue to use an
691        element now "orphan" as long as you have a reference to it.
692
693        For TextChangeStart : delete also the end tag if exists.
694
695        Arguments:
696
697            child -- Element
698
699            keep_tail -- boolean (default to True), True for most usages.
700        """
701        if child is not None:  # act like normal delete
702            return super().delete(child, keep_tail)
703        idx = self.get_id()
704        parent = self.parent
705        if parent is None:
706            raise ValueError("cannot delete the root element")
707        body = self.document_body
708        if not body:
709            body = parent
710        end = body.get_text_change_end(idx=idx)
711        if end:
712            end.delete()
713        # act like normal delete
714        super().delete()

Delete the given element from the XML tree. If no element is given, "self" is deleted. The XML library may allow to continue to use an element now "orphan" as long as you have a reference to it.

For TextChangeStart : delete also the end tag if exists.

Arguments:

child -- Element

keep_tail -- boolean (default to True), True for most usages.
Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
TextChangeEnd
get_deleted
get_inserted
TextChange
get_id
set_id
get_changed_region
get_change_info
get_change_element
class TextChangedRegion(odfdo.Element):
358class TextChangedRegion(Element):
359    """Each TextChangedRegion "text:changed-region" element contains a single
360    element, one of TextInsertion, TextDeletion or TextFormatChange that
361    corresponds to a change being tracked within the scope of the
362    "text:tracked-changes" element that contains the "text:changed-region"
363    instance.
364    The xml:id attribute of the TextChangedRegion is referenced
365    from the "text:change", "text:change-start" and "text:change-end"
366    elements that identify where the change applies to markup in the scope of
367    the "text:tracked-changes" element.
368
369    Warning : for this implementation, text:change should be referenced only
370              once in the scope, which is different from ODF 1.2 requirement:
371             " A "text:changed-region" can be referenced by more than one
372             change, but the corresponding referencing change mark elements
373             shall be of the same change type - insertion, format change or
374             deletion. "
375    """
376
377    _tag = "text:changed-region"
378
379    def get_change_info(self) -> Element | None:
380        """Shortcut to get the ChangeInfo element of the change
381        element child.
382
383        Return: ChangeInfo element.
384        """
385        return self.get_element("descendant::office:change-info")
386
387    def set_change_info(
388        self,
389        change_info: Element | None = None,
390        creator: str | None = None,
391        date: datetime | None = None,
392        comments: Element | list[Element] | None = None,
393    ) -> None:
394        """Shortcut to set the ChangeInfo element of the sub change element.
395        See TextInsertion.set_change_info() for details.
396
397        Arguments:
398
399             change_info -- ChangeInfo element (or None)
400
401             cretor -- str (or None)
402
403             date -- datetime (or None)
404
405             comments -- Paragraph or list of Paragraph elements (or None)
406        """
407        child = self.get_change_element()
408        if not child:
409            raise ValueError
410        child.set_change_info(  # type: ignore
411            change_info=change_info, creator=creator, date=date, comments=comments
412        )
413
414    def get_change_element(self) -> Element | None:
415        """Get the change element child. It can be either: TextInsertion,
416        TextDeletion, or TextFormatChange as an Element object.
417
418        Return: Element.
419        """
420        request = (
421            "descendant::text:insertion "
422            "| descendant::text:deletion"
423            "| descendant::text:format-change"
424        )
425        return self._filtered_element(request, 0)
426
427    def _get_text_id(self) -> str | None:
428        return self.get_attribute_string("text:id")
429
430    def _set_text_id(self, text_id: str) -> None:
431        self.set_attribute("text:id", text_id)
432
433    def _get_xml_id(self) -> str | None:
434        return self.get_attribute_string("xml:id")
435
436    def _set_xml_id(self, xml_id: str) -> None:
437        self.set_attribute("xml:id", xml_id)
438
439    def get_id(self) -> str | None:
440        """Get the "text:id" attribute.
441
442        Return: str
443        """
444        return self._get_text_id()
445
446    def set_id(self, idx: str) -> None:
447        """Set both the "text:id" and "xml:id" attributes with same value."""
448        self._set_text_id(idx)
449        self._set_xml_id(idx)

Each TextChangedRegion "text:changed-region" element contains a single element, one of TextInsertion, TextDeletion or TextFormatChange that corresponds to a change being tracked within the scope of the "text:tracked-changes" element that contains the "text:changed-region" instance. The xml:id attribute of the TextChangedRegion is referenced from the "text:change", "text:change-start" and "text:change-end" elements that identify where the change applies to markup in the scope of the "text:tracked-changes" element.

Warning : for this implementation, text:change should be referenced only once in the scope, which is different from ODF 1.2 requirement: " A "text:changed-region" can be referenced by more than one change, but the corresponding referencing change mark elements shall be of the same change type - insertion, format change or deletion. "

def get_change_info(self) -> Element | None:
379    def get_change_info(self) -> Element | None:
380        """Shortcut to get the ChangeInfo element of the change
381        element child.
382
383        Return: ChangeInfo element.
384        """
385        return self.get_element("descendant::office:change-info")

Shortcut to get the ChangeInfo element of the change element child.

Return: ChangeInfo element.

def set_change_info( self, change_info: Element | None = None, creator: str | None = None, date: datetime.datetime | None = None, comments: Element | list[Element] | None = None) -> None:
387    def set_change_info(
388        self,
389        change_info: Element | None = None,
390        creator: str | None = None,
391        date: datetime | None = None,
392        comments: Element | list[Element] | None = None,
393    ) -> None:
394        """Shortcut to set the ChangeInfo element of the sub change element.
395        See TextInsertion.set_change_info() for details.
396
397        Arguments:
398
399             change_info -- ChangeInfo element (or None)
400
401             cretor -- str (or None)
402
403             date -- datetime (or None)
404
405             comments -- Paragraph or list of Paragraph elements (or None)
406        """
407        child = self.get_change_element()
408        if not child:
409            raise ValueError
410        child.set_change_info(  # type: ignore
411            change_info=change_info, creator=creator, date=date, comments=comments
412        )

Shortcut to set the ChangeInfo element of the sub change element. See TextInsertion.set_change_info() for details.

Arguments:

 change_info -- ChangeInfo element (or None)

 cretor -- str (or None)

 date -- datetime (or None)

 comments -- Paragraph or list of Paragraph elements (or None)
def get_change_element(self) -> Element | None:
414    def get_change_element(self) -> Element | None:
415        """Get the change element child. It can be either: TextInsertion,
416        TextDeletion, or TextFormatChange as an Element object.
417
418        Return: Element.
419        """
420        request = (
421            "descendant::text:insertion "
422            "| descendant::text:deletion"
423            "| descendant::text:format-change"
424        )
425        return self._filtered_element(request, 0)

Get the change element child. It can be either: TextInsertion, TextDeletion, or TextFormatChange as an Element object.

Return: Element.

def get_id(self) -> str | None:
439    def get_id(self) -> str | None:
440        """Get the "text:id" attribute.
441
442        Return: str
443        """
444        return self._get_text_id()

Get the "text:id" attribute.

Return: str

def set_id(self, idx: str) -> None:
446    def set_id(self, idx: str) -> None:
447        """Set both the "text:id" and "xml:id" attributes with same value."""
448        self._set_text_id(idx)
449        self._set_xml_id(idx)

Set both the "text:id" and "xml:id" attributes with same value.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TextDeletion(odfdo.TextInsertion):
251class TextDeletion(TextInsertion):
252    """The TextDeletion "text:deletion" contains information that identifies
253    the person responsible for a deletion and the date of that deletion.
254    This information may also contain one or more Paragraph which contains
255    a comment on the deletion. The TextDeletion element may also contain
256    content that was deleted while change tracking was enabled. The position
257    where the text was deleted is marked by a "text:change" element. Deleted
258    text is contained in a paragraph element. To reconstruct the original
259    text, the paragraph containing the deleted text is merged with its
260    surrounding paragraph or heading element. To reconstruct the text before
261    a deletion took place:
262      - If the change mark is inside a paragraph, insert the content that was
263      deleted, but remove all leading start tags up to and including the
264      first "text:p" element and all trailing end tags up to and including
265      the last "/text:p" or "/text:h" element. If the last trailing element
266      is a "/text:h", change the end tag "/text:p" following this insertion
267      to a "/text:h" element.
268      - If the change mark is inside a heading, insert the content that was
269      deleted, but remove all leading start tags up to and including the
270      first "text:h" element and all trailing end tags up to and including
271      the last "/text:h" or "/text:p" element. If the last trailing element
272      is a "/text:p", change the end tag "/text:h" following this insertion
273      to a "/text:p" element.
274      - Otherwise, copy the text content of the "text:deletion" element in
275      place of the change mark.
276    """
277
278    _tag = "text:deletion"
279
280    def get_deleted(
281        self,
282        as_text: bool = False,
283        no_header: bool = False,
284    ) -> str | list[Element] | None:
285        """Get the deleted informations stored in the TextDeletion.
286        If as_text is True: returns the text content.
287        If no_header is True: existing Heading are changed in Paragraph
288
289        Arguments:
290
291            as_text -- boolean
292
293            no_header -- boolean
294
295        Return: Paragraph and Header list
296        """
297        children = self.children
298        inner = [elem for elem in children if elem.tag != "office:change-info"]
299        if no_header:  # crude replace t:h by t:p
300            new_inner = []
301            for element in inner:
302                if element.tag == "text:h":
303                    children = element.children
304                    text = element.text
305                    para = Element.from_tag("text:p")
306                    para.text = text
307                    for child in children:
308                        para.append(child)
309                    new_inner.append(para)
310                else:
311                    new_inner.append(element)
312            inner = new_inner
313        if as_text:
314            return "\n".join([elem.get_formatted_text(context=None) for elem in inner])
315        return inner
316
317    def set_deleted(self, paragraph_or_list: Element | list[Element]) -> None:
318        """Set the deleted informations stored in the TextDeletion. An
319        actual content that was deleted is expected, embeded in a Paragraph
320        element or Header.
321
322        Arguments:
323
324            paragraph_or_list -- Paragraph or Header element (or list)
325        """
326        for element in self.get_deleted():  # type: ignore
327            self.delete(element)  # type: ignore
328        if isinstance(paragraph_or_list, Element):
329            paragraph_or_list = [paragraph_or_list]
330        for element in paragraph_or_list:
331            self.append(element)
332
333    def get_inserted(
334        self,
335        as_text: bool = False,
336        no_header: bool = False,
337        clean: bool = True,
338    ) -> str | Element | list[Element] | None:
339        """Return None."""
340        if as_text:
341            return ""
342        return None

The TextDeletion "text:deletion" contains information that identifies the person responsible for a deletion and the date of that deletion. This information may also contain one or more Paragraph which contains a comment on the deletion. The TextDeletion element may also contain content that was deleted while change tracking was enabled. The position where the text was deleted is marked by a "text:change" element. Deleted text is contained in a paragraph element. To reconstruct the original text, the paragraph containing the deleted text is merged with its surrounding paragraph or heading element. To reconstruct the text before a deletion took place:

  • If the change mark is inside a paragraph, insert the content that was deleted, but remove all leading start tags up to and including the first "text:p" element and all trailing end tags up to and including the last "/text:p" or "/text:h" element. If the last trailing element is a "/text:h", change the end tag "/text:p" following this insertion to a "/text:h" element.
  • If the change mark is inside a heading, insert the content that was deleted, but remove all leading start tags up to and including the first "text:h" element and all trailing end tags up to and including the last "/text:h" or "/text:p" element. If the last trailing element is a "/text:p", change the end tag "/text:h" following this insertion to a "/text:p" element.
  • Otherwise, copy the text content of the "text:deletion" element in place of the change mark.
def get_deleted( self, as_text: bool = False, no_header: bool = False) -> str | list[Element] | None:
280    def get_deleted(
281        self,
282        as_text: bool = False,
283        no_header: bool = False,
284    ) -> str | list[Element] | None:
285        """Get the deleted informations stored in the TextDeletion.
286        If as_text is True: returns the text content.
287        If no_header is True: existing Heading are changed in Paragraph
288
289        Arguments:
290
291            as_text -- boolean
292
293            no_header -- boolean
294
295        Return: Paragraph and Header list
296        """
297        children = self.children
298        inner = [elem for elem in children if elem.tag != "office:change-info"]
299        if no_header:  # crude replace t:h by t:p
300            new_inner = []
301            for element in inner:
302                if element.tag == "text:h":
303                    children = element.children
304                    text = element.text
305                    para = Element.from_tag("text:p")
306                    para.text = text
307                    for child in children:
308                        para.append(child)
309                    new_inner.append(para)
310                else:
311                    new_inner.append(element)
312            inner = new_inner
313        if as_text:
314            return "\n".join([elem.get_formatted_text(context=None) for elem in inner])
315        return inner

Get the deleted informations stored in the TextDeletion. If as_text is True: returns the text content. If no_header is True: existing Heading are changed in Paragraph

Arguments:

as_text -- boolean

no_header -- boolean

Return: Paragraph and Header list

def set_deleted( self, paragraph_or_list: Element | list[Element]) -> None:
317    def set_deleted(self, paragraph_or_list: Element | list[Element]) -> None:
318        """Set the deleted informations stored in the TextDeletion. An
319        actual content that was deleted is expected, embeded in a Paragraph
320        element or Header.
321
322        Arguments:
323
324            paragraph_or_list -- Paragraph or Header element (or list)
325        """
326        for element in self.get_deleted():  # type: ignore
327            self.delete(element)  # type: ignore
328        if isinstance(paragraph_or_list, Element):
329            paragraph_or_list = [paragraph_or_list]
330        for element in paragraph_or_list:
331            self.append(element)

Set the deleted informations stored in the TextDeletion. An actual content that was deleted is expected, embeded in a Paragraph element or Header.

Arguments:

paragraph_or_list -- Paragraph or Header element (or list)
def get_inserted( self, as_text: bool = False, no_header: bool = False, clean: bool = True) -> str | Element | list[Element] | None:
333    def get_inserted(
334        self,
335        as_text: bool = False,
336        no_header: bool = False,
337        clean: bool = True,
338    ) -> str | Element | list[Element] | None:
339        """Return None."""
340        if as_text:
341            return ""
342        return None

Return None.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
TextInsertion
get_change_info
set_change_info
class TextFormatChange(odfdo.TextInsertion):
345class TextFormatChange(TextInsertion):
346    """The TextFormatChange "text:format-change" element represents any change
347    in formatting attributes. The region where the change took place is
348    marked by "text:change-start", "text:change-end" or "text:change"
349    elements.
350
351    Note: This element does not contain formatting changes that have taken
352    place.
353    """
354
355    _tag = "text:format-change"

The TextFormatChange "text:format-change" element represents any change in formatting attributes. The region where the change took place is marked by "text:change-start", "text:change-end" or "text:change" elements.

Note: This element does not contain formatting changes that have taken place.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
TextInsertion
get_deleted
get_inserted
get_change_info
set_change_info
class TextInsertion(odfdo.Element):
134class TextInsertion(Element):
135    """The TextInsertion "text:insertion" element contains the information
136    that identifies the person responsible for a change and the date of
137    that change. This information may also contain one or more "text:p"
138    Paragraph which contain a comment on the insertion. The
139    TextInsertion element's parent "text:changed-region" element has an
140    xml:id or text:id attribute, the value of which binds that parent
141    element to the text:change-id attribute on the "text:change-start"
142    and "text:change-end" elements.
143    """
144
145    _tag = "text:insertion"
146
147    def get_deleted(
148        self,
149        as_text: bool = False,
150        no_header: bool = False,
151    ) -> str | list[Element] | None:
152        """Return: None."""
153        if as_text:
154            return ""
155        return None
156
157    def get_inserted(
158        self,
159        as_text: bool = False,
160        no_header: bool = False,
161        clean: bool = True,
162    ) -> str | Element | list[Element] | None:
163        """Shortcut to text:change-start.get_inserted(). Return the content
164        between text:change-start and text:change-end.
165
166        If as_text is True: returns the text content.
167        If no_header is True: existing Heading are changed in Paragraph
168        If no_header is True: existing text:h are changed in text:p
169        By default: returns a list of Element, cleaned and with headers
170
171        Arguments:
172
173            as_text -- boolean
174
175            clean -- boolean
176
177            no_header -- boolean
178
179        Return: list or Element or text
180        """
181        current = self.parent  # text:changed-region
182        if not current:
183            raise ValueError
184        idx = current.get_id()  # type: ignore
185        body = self.document_body
186        if not body:
187            body = self.root
188        text_change = body.get_text_change_start(idx=idx)
189        if not text_change:
190            raise ValueError
191        return text_change.get_inserted(  # type: ignore
192            as_text=as_text, no_header=no_header, clean=clean
193        )
194
195    def get_change_info(self) -> Element | None:
196        """Get the ChangeInfo child of the element.
197
198        Return: ChangeInfo element.
199        """
200        return self.get_element("descendant::office:change-info")
201
202    def set_change_info(
203        self,
204        change_info: Element | None = None,
205        creator: str | None = None,
206        date: datetime | None = None,
207        comments: Element | list[Element] | None = None,
208    ) -> None:
209        """Set the ChangeInfo element for the change element. If change_info
210        is not provided, creator, date and comments will be used to build a
211        suitable change info element. Default for creator is 'Unknown',
212        default for date is current time and default for comments is no
213        comment at all.
214        The new change info element will replace any existant ChangeInfo.
215
216        Arguments:
217
218             change_info -- ChangeInfo element (or None)
219
220             cretor -- str (or None)
221
222             date -- datetime (or None)
223
224             comments -- Paragraph or list of Paragraph elements (or None)
225        """
226        if change_info is None:
227            new_change_info = ChangeInfo(creator, date)
228            if comments is not None:
229                if isinstance(comments, Element):
230                    # single pararagraph comment
231                    comments_list = [comments]
232                else:
233                    comments_list = comments
234                # assume iterable of Paragraph
235                for paragraph in comments_list:
236                    if not isinstance(paragraph, Paragraph):
237                        raise TypeError(f"Not a Paragraph: '{paragraph!r}'")
238                    new_change_info.insert(paragraph, xmlposition=LAST_CHILD)
239        else:
240            if not isinstance(change_info, ChangeInfo):
241                raise TypeError(f"Not a ChangeInfo: '{change_info!r}'")
242            new_change_info = change_info
243
244        old = self.get_change_info()
245        if old is not None:
246            self.replace_element(old, new_change_info)
247        else:
248            self.insert(new_change_info, xmlposition=FIRST_CHILD)

The TextInsertion "text:insertion" element contains the information that identifies the person responsible for a change and the date of that change. This information may also contain one or more "text:p" Paragraph which contain a comment on the insertion. The TextInsertion element's parent "text:changed-region" element has an xml:id or text:id attribute, the value of which binds that parent element to the text:change-id attribute on the "text:change-start" and "text:change-end" elements.

def get_deleted( self, as_text: bool = False, no_header: bool = False) -> str | list[Element] | None:
147    def get_deleted(
148        self,
149        as_text: bool = False,
150        no_header: bool = False,
151    ) -> str | list[Element] | None:
152        """Return: None."""
153        if as_text:
154            return ""
155        return None

Return: None.

def get_inserted( self, as_text: bool = False, no_header: bool = False, clean: bool = True) -> str | Element | list[Element] | None:
157    def get_inserted(
158        self,
159        as_text: bool = False,
160        no_header: bool = False,
161        clean: bool = True,
162    ) -> str | Element | list[Element] | None:
163        """Shortcut to text:change-start.get_inserted(). Return the content
164        between text:change-start and text:change-end.
165
166        If as_text is True: returns the text content.
167        If no_header is True: existing Heading are changed in Paragraph
168        If no_header is True: existing text:h are changed in text:p
169        By default: returns a list of Element, cleaned and with headers
170
171        Arguments:
172
173            as_text -- boolean
174
175            clean -- boolean
176
177            no_header -- boolean
178
179        Return: list or Element or text
180        """
181        current = self.parent  # text:changed-region
182        if not current:
183            raise ValueError
184        idx = current.get_id()  # type: ignore
185        body = self.document_body
186        if not body:
187            body = self.root
188        text_change = body.get_text_change_start(idx=idx)
189        if not text_change:
190            raise ValueError
191        return text_change.get_inserted(  # type: ignore
192            as_text=as_text, no_header=no_header, clean=clean
193        )

Shortcut to text:change-start.get_inserted(). Return the content between text:change-start and text:change-end.

If as_text is True: returns the text content. If no_header is True: existing Heading are changed in Paragraph If no_header is True: existing text:h are changed in text:p By default: returns a list of Element, cleaned and with headers

Arguments:

as_text -- boolean

clean -- boolean

no_header -- boolean

Return: list or Element or text

def get_change_info(self) -> Element | None:
195    def get_change_info(self) -> Element | None:
196        """Get the ChangeInfo child of the element.
197
198        Return: ChangeInfo element.
199        """
200        return self.get_element("descendant::office:change-info")

Get the ChangeInfo child of the element.

Return: ChangeInfo element.

def set_change_info( self, change_info: Element | None = None, creator: str | None = None, date: datetime.datetime | None = None, comments: Element | list[Element] | None = None) -> None:
202    def set_change_info(
203        self,
204        change_info: Element | None = None,
205        creator: str | None = None,
206        date: datetime | None = None,
207        comments: Element | list[Element] | None = None,
208    ) -> None:
209        """Set the ChangeInfo element for the change element. If change_info
210        is not provided, creator, date and comments will be used to build a
211        suitable change info element. Default for creator is 'Unknown',
212        default for date is current time and default for comments is no
213        comment at all.
214        The new change info element will replace any existant ChangeInfo.
215
216        Arguments:
217
218             change_info -- ChangeInfo element (or None)
219
220             cretor -- str (or None)
221
222             date -- datetime (or None)
223
224             comments -- Paragraph or list of Paragraph elements (or None)
225        """
226        if change_info is None:
227            new_change_info = ChangeInfo(creator, date)
228            if comments is not None:
229                if isinstance(comments, Element):
230                    # single pararagraph comment
231                    comments_list = [comments]
232                else:
233                    comments_list = comments
234                # assume iterable of Paragraph
235                for paragraph in comments_list:
236                    if not isinstance(paragraph, Paragraph):
237                        raise TypeError(f"Not a Paragraph: '{paragraph!r}'")
238                    new_change_info.insert(paragraph, xmlposition=LAST_CHILD)
239        else:
240            if not isinstance(change_info, ChangeInfo):
241                raise TypeError(f"Not a ChangeInfo: '{change_info!r}'")
242            new_change_info = change_info
243
244        old = self.get_change_info()
245        if old is not None:
246            self.replace_element(old, new_change_info)
247        else:
248            self.insert(new_change_info, xmlposition=FIRST_CHILD)

Set the ChangeInfo element for the change element. If change_info is not provided, creator, date and comments will be used to build a suitable change info element. Default for creator is 'Unknown', default for date is current time and default for comments is no comment at all. The new change info element will replace any existant ChangeInfo.

Arguments:

 change_info -- ChangeInfo element (or None)

 cretor -- str (or None)

 date -- datetime (or None)

 comments -- Paragraph or list of Paragraph elements (or None)
Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TocEntryTemplate(odfdo.Element):
467class TocEntryTemplate(Element):
468    """ODF "text:table-of-content-entry-template"
469
470    Arguments:
471
472        style -- str
473    """
474
475    _tag = "text:table-of-content-entry-template"
476    _properties = (PropDef("style", "text:style-name"),)
477
478    def __init__(
479        self,
480        style: str | None = None,
481        outline_level: int | None = None,
482        **kwargs: Any,
483    ) -> None:
484        super().__init__(**kwargs)
485        if self._do_init:
486            if style:
487                self.style = style
488            if outline_level:
489                self.outline_level = outline_level
490
491    @property
492    def outline_level(self) -> int | None:
493        return self.get_attribute_integer("text:outline-level")
494
495    @outline_level.setter
496    def outline_level(self, level: int) -> None:
497        self.set_attribute("text:outline-level", str(level))
498
499    def complete_defaults(self) -> None:
500        self.append(Element.from_tag("text:index-entry-chapter"))
501        self.append(Element.from_tag("text:index-entry-text"))
502        self.append(Element.from_tag("text:index-entry-text"))
503        ts = Element.from_tag("text:index-entry-text")
504        ts.set_style_attribute("style:type", "right")
505        ts.set_style_attribute("style:leader-char", ".")
506        self.append(ts)
507        self.append(Element.from_tag("text:index-entry-page-number"))

ODF "text:table-of-content-entry-template"

Arguments:

style -- str
TocEntryTemplate( style: str | None = None, outline_level: int | None = None, **kwargs: Any)
478    def __init__(
479        self,
480        style: str | None = None,
481        outline_level: int | None = None,
482        **kwargs: Any,
483    ) -> None:
484        super().__init__(**kwargs)
485        if self._do_init:
486            if style:
487                self.style = style
488            if outline_level:
489                self.outline_level = outline_level
outline_level: int | None
491    @property
492    def outline_level(self) -> int | None:
493        return self.get_attribute_integer("text:outline-level")
def complete_defaults(self) -> None:
499    def complete_defaults(self) -> None:
500        self.append(Element.from_tag("text:index-entry-chapter"))
501        self.append(Element.from_tag("text:index-entry-text"))
502        self.append(Element.from_tag("text:index-entry-text"))
503        ts = Element.from_tag("text:index-entry-text")
504        ts.set_style_attribute("style:type", "right")
505        ts.set_style_attribute("style:leader-char", ".")
506        self.append(ts)
507        self.append(Element.from_tag("text:index-entry-page-number"))
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class TrackedChanges(odfdo.Element):
452class TrackedChanges(Element):
453    """The TrackedChanges "text:tracked-changes" element acts as a container
454    for TextChangedRegion elements that represent changes in a certain
455    scope of an OpenDocument document. This scope is the element in which
456    the TrackedChanges element occurs. Changes in this scope shall be
457    tracked by TextChangedRegion elements contained in the
458    TrackedChanges element in this scope. If a TrackedChanges
459    element is absent, there are no tracked changes in the corresponding
460    scope. In this case, all change mark elements in this scope shall be
461    ignored.
462    """
463
464    _tag = "text:tracked-changes"
465
466    def get_changed_regions(
467        self,
468        creator: str | None = None,
469        date: datetime | None = None,
470        content: str | None = None,
471        role: str | None = None,
472    ) -> list[Element]:
473        changed_regions = self._filtered_elements(
474            "text:changed-region",
475            dc_creator=creator,
476            dc_date=date,
477            content=content,
478        )
479        if role is None:
480            return changed_regions
481        result: list[Element] = []
482        for regien in changed_regions:
483            changed = regien.get_change_element()  # type: ignore
484            if not changed:
485                continue
486            if changed.tag.endswith(role):
487                result.append(regien)
488        return result
489
490    def get_changed_region(
491        self,
492        position: int = 0,
493        text_id: str | None = None,
494        creator: str | None = None,
495        date: datetime | None = None,
496        content: str | None = None,
497    ) -> Element | None:
498        return self._filtered_element(
499            "text:changed-region",
500            position,
501            text_id=text_id,
502            dc_creator=creator,
503            dc_date=date,
504            content=content,
505        )

The TrackedChanges "text:tracked-changes" element acts as a container for TextChangedRegion elements that represent changes in a certain scope of an OpenDocument document. This scope is the element in which the TrackedChanges element occurs. Changes in this scope shall be tracked by TextChangedRegion elements contained in the TrackedChanges element in this scope. If a TrackedChanges element is absent, there are no tracked changes in the corresponding scope. In this case, all change mark elements in this scope shall be ignored.

def get_changed_regions( self, creator: str | None = None, date: datetime.datetime | None = None, content: str | None = None, role: str | None = None) -> list[Element]:
466    def get_changed_regions(
467        self,
468        creator: str | None = None,
469        date: datetime | None = None,
470        content: str | None = None,
471        role: str | None = None,
472    ) -> list[Element]:
473        changed_regions = self._filtered_elements(
474            "text:changed-region",
475            dc_creator=creator,
476            dc_date=date,
477            content=content,
478        )
479        if role is None:
480            return changed_regions
481        result: list[Element] = []
482        for regien in changed_regions:
483            changed = regien.get_change_element()  # type: ignore
484            if not changed:
485                continue
486            if changed.tag.endswith(role):
487                result.append(regien)
488        return result
def get_changed_region( self, position: int = 0, text_id: str | None = None, creator: str | None = None, date: datetime.datetime | None = None, content: str | None = None) -> Element | None:
490    def get_changed_region(
491        self,
492        position: int = 0,
493        text_id: str | None = None,
494        creator: str | None = None,
495        date: datetime | None = None,
496        content: str | None = None,
497    ) -> Element | None:
498        return self._filtered_element(
499            "text:changed-region",
500            position,
501            text_id=text_id,
502            dc_creator=creator,
503            dc_date=date,
504            content=content,
505        )
Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserDefined(odfdo.ElementTyped):
212class UserDefined(ElementTyped):
213    """Return a user defined field "text:user-defined". If the current
214    document is provided, try to extract the content of the meta user defined
215    field of same name.
216
217    Arguments:
218
219        name -- str, name of the user defined field
220
221        value -- python typed value, value of the field
222
223        value_type -- str, office:value-type known type
224
225        text -- str
226
227        style -- str
228
229        from_document -- ODF document
230    """
231
232    _tag = "text:user-defined"
233    _properties = (
234        PropDef("name", "text:name"),
235        PropDef("style", "style:data-style-name"),
236    )
237
238    def __init__(
239        self,
240        name: str = "",
241        value: Any = None,
242        value_type: str | None = None,
243        text: str | None = None,
244        style: str | None = None,
245        from_document: Document | None = None,
246        **kwargs: Any,
247    ) -> None:
248        super().__init__(**kwargs)
249        if self._do_init:
250            if name:
251                self.name = name
252            if style:
253                self.style = style
254            if from_document is not None:
255                meta_infos = from_document.meta
256                content = meta_infos.get_user_defined_metadata_of_name(name)
257                if content is not None:
258                    value = content.get("value", None)
259                    value_type = content.get("value_type", None)
260                    text = content.get("text", None)
261            text = self.set_value_and_type(
262                value=value, value_type=value_type, text=text
263            )
264            self.text = text  # type: ignore

Return a user defined field "text:user-defined". If the current document is provided, try to extract the content of the meta user defined field of same name.

Arguments:

name -- str, name of the user defined field

value -- python typed value, value of the field

value_type -- str, office:value-type known type

text -- str

style -- str

from_document -- ODF document
UserDefined( name: str = '', value: Any = None, value_type: str | None = None, text: str | None = None, style: str | None = None, from_document: Document | None = None, **kwargs: Any)
238    def __init__(
239        self,
240        name: str = "",
241        value: Any = None,
242        value_type: str | None = None,
243        text: str | None = None,
244        style: str | None = None,
245        from_document: Document | None = None,
246        **kwargs: Any,
247    ) -> None:
248        super().__init__(**kwargs)
249        if self._do_init:
250            if name:
251                self.name = name
252            if style:
253                self.style = style
254            if from_document is not None:
255                meta_infos = from_document.meta
256                content = meta_infos.get_user_defined_metadata_of_name(name)
257                if content is not None:
258                    value = content.get("value", None)
259                    value_type = content.get("value_type", None)
260                    text = content.get("text", None)
261            text = self.set_value_and_type(
262                value=value, value_type=value_type, text=text
263            )
264            self.text = text  # type: ignore
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserFieldDecl(odfdo.ElementTyped):
146class UserFieldDecl(ElementTyped):
147    _tag = "text:user-field-decl"
148    _properties = (PropDef("name", "text:name"),)
149
150    def __init__(
151        self,
152        name: str | None = None,
153        value: Any = None,
154        value_type: str | None = None,
155        **kwargs: Any,
156    ) -> None:
157        super().__init__(**kwargs)
158        if self._do_init:
159            if name:
160                self.name = name
161            self.set_value_and_type(value=value, value_type=value_type)
162
163    def set_value(self, value: Any) -> None:
164        name = self.get_attribute("text:name")
165        self.clear()
166        self.set_value_and_type(value=value)
167        self.set_attribute("text:name", name)

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

UserFieldDecl( name: str | None = None, value: Any = None, value_type: str | None = None, **kwargs: Any)
150    def __init__(
151        self,
152        name: str | None = None,
153        value: Any = None,
154        value_type: str | None = None,
155        **kwargs: Any,
156    ) -> None:
157        super().__init__(**kwargs)
158        if self._do_init:
159            if name:
160                self.name = name
161            self.set_value_and_type(value=value, value_type=value_type)
def set_value(self, value: Any) -> None:
163    def set_value(self, value: Any) -> None:
164        name = self.get_attribute("text:name")
165        self.clear()
166        self.set_value_and_type(value=value)
167        self.set_attribute("text:name", name)
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserFieldDecls(odfdo.Element):
142class UserFieldDecls(Element):
143    _tag = "text:user-field-decls"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserFieldGet(odfdo.ElementTyped):
173class UserFieldGet(ElementTyped):
174    _tag = "text:user-field-get"
175    _properties = (
176        PropDef("name", "text:name"),
177        PropDef("style", "style:data-style-name"),
178    )
179
180    def __init__(
181        self,
182        name: str | None = None,
183        value: Any = None,
184        value_type: str | None = None,
185        text: str | None = None,
186        style: str | None = None,
187        **kwargs: Any,
188    ) -> None:
189        super().__init__(**kwargs)
190        if self._do_init:
191            if name:
192                self.name = name
193            text = self.set_value_and_type(
194                value=value, value_type=value_type, text=text
195            )
196            self.text = text  # type: ignore
197
198            if style:
199                self.style = style

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

UserFieldGet( name: str | None = None, value: Any = None, value_type: str | None = None, text: str | None = None, style: str | None = None, **kwargs: Any)
180    def __init__(
181        self,
182        name: str | None = None,
183        value: Any = None,
184        value_type: str | None = None,
185        text: str | None = None,
186        style: str | None = None,
187        **kwargs: Any,
188    ) -> None:
189        super().__init__(**kwargs)
190        if self._do_init:
191            if name:
192                self.name = name
193            text = self.set_value_and_type(
194                value=value, value_type=value_type, text=text
195            )
196            self.text = text  # type: ignore
197
198            if style:
199                self.style = style
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class UserFieldInput(odfdo.UserFieldGet):
205class UserFieldInput(UserFieldGet):
206    _tag = "text:user-field-input"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
UserFieldGet
UserFieldGet
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarChapter(odfdo.Element):
376class VarChapter(Element):
377    _tag = "text:chapter"
378    _properties = (
379        PropDef("display", "text:display"),
380        PropDef("outline_level", "text:outline-level"),
381    )
382    DISPLAY_VALUE_CHOICE = {  # noqa: RUF012
383        "number",
384        "name",
385        "number-and-name",
386        "plain-number",
387        "plain-number-and-name",
388    }
389
390    def __init__(
391        self,
392        display: str | None = "name",
393        outline_level: str | None = None,
394        **kwargs: Any,
395    ) -> None:
396        """display can be: 'number', 'name', 'number-and-name', 'plain-number' or
397        'plain-number-and-name'
398        """
399        super().__init__(**kwargs)
400        if self._do_init:
401            if display not in VarChapter.DISPLAY_VALUE_CHOICE:
402                raise ValueError("unknown display value: %s" % display)
403            self.display = display
404            if outline_level is not None:
405                self.outline_level = outline_level

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarChapter( display: str | None = 'name', outline_level: str | None = None, **kwargs: Any)
390    def __init__(
391        self,
392        display: str | None = "name",
393        outline_level: str | None = None,
394        **kwargs: Any,
395    ) -> None:
396        """display can be: 'number', 'name', 'number-and-name', 'plain-number' or
397        'plain-number-and-name'
398        """
399        super().__init__(**kwargs)
400        if self._do_init:
401            if display not in VarChapter.DISPLAY_VALUE_CHOICE:
402                raise ValueError("unknown display value: %s" % display)
403            self.display = display
404            if outline_level is not None:
405                self.outline_level = outline_level

display can be: 'number', 'name', 'number-and-name', 'plain-number' or 'plain-number-and-name'

DISPLAY_VALUE_CHOICE = {'number', 'name', 'plain-number', 'number-and-name', 'plain-number-and-name'}
display: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
outline_level: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarCreationDate(odfdo.Element):
456class VarCreationDate(Element):
457    _tag = "text:creation-date"
458    _properties = (
459        PropDef("fixed", "text:fixed"),
460        PropDef("data_style", "style:data-style-name"),
461    )
462
463    def __init__(
464        self,
465        fixed: bool = False,
466        data_style: str | None = None,
467        **kwargs: Any,
468    ) -> None:
469        super().__init__(**kwargs)
470        if self._do_init:
471            if fixed:
472                self.fixed = True
473            if data_style:
474                self.data_style = data_style

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarCreationDate(fixed: bool = False, data_style: str | None = None, **kwargs: Any)
463    def __init__(
464        self,
465        fixed: bool = False,
466        data_style: str | None = None,
467        **kwargs: Any,
468    ) -> None:
469        super().__init__(**kwargs)
470        if self._do_init:
471            if fixed:
472                self.fixed = True
473            if data_style:
474                self.data_style = data_style
fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
data_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarCreationTime(odfdo.Element):
480class VarCreationTime(Element):
481    _tag = "text:creation-time"
482    _properties = (
483        PropDef("fixed", "text:fixed"),
484        PropDef("data_style", "style:data-style-name"),
485    )
486
487    def __init__(
488        self,
489        fixed: bool = False,
490        data_style: str | None = None,
491        **kwargs: Any,
492    ) -> None:
493        super().__init__(**kwargs)
494        if self._do_init:
495            if fixed:
496                self.fixed = True
497            if data_style:
498                self.data_style = data_style

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarCreationTime(fixed: bool = False, data_style: str | None = None, **kwargs: Any)
487    def __init__(
488        self,
489        fixed: bool = False,
490        data_style: str | None = None,
491        **kwargs: Any,
492    ) -> None:
493        super().__init__(**kwargs)
494        if self._do_init:
495            if fixed:
496                self.fixed = True
497            if data_style:
498                self.data_style = data_style
fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
data_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarDate(odfdo.Element):
305class VarDate(Element):
306    _tag = "text:date"
307    _properties = (
308        PropDef("date", "text:date-value"),
309        PropDef("fixed", "text:fixed"),
310        PropDef("data_style", "style:data-style-name"),
311        PropDef("date_adjust", "text:date-adjust"),
312    )
313
314    def __init__(
315        self,
316        date: datetime | None = None,
317        fixed: bool = False,
318        data_style: str | None = None,
319        text: str | None = None,
320        date_adjust: timedelta | None = None,
321        **kwargs: Any,
322    ) -> None:
323        super().__init__(**kwargs)
324        if self._do_init:
325            if date:
326                self.date = DateTime.encode(date)
327            if fixed:
328                self.fixed = True
329            if data_style is not None:
330                self.data_style = data_style
331            if text is None and date is not None:
332                text = Date.encode(date)
333            self.text = text  # type: ignore
334            if date_adjust is not None:
335                self.date_adjust = Duration.encode(date_adjust)

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarDate( date: datetime.datetime | None = None, fixed: bool = False, data_style: str | None = None, text: str | None = None, date_adjust: datetime.timedelta | None = None, **kwargs: Any)
314    def __init__(
315        self,
316        date: datetime | None = None,
317        fixed: bool = False,
318        data_style: str | None = None,
319        text: str | None = None,
320        date_adjust: timedelta | None = None,
321        **kwargs: Any,
322    ) -> None:
323        super().__init__(**kwargs)
324        if self._do_init:
325            if date:
326                self.date = DateTime.encode(date)
327            if fixed:
328                self.fixed = True
329            if data_style is not None:
330                self.data_style = data_style
331            if text is None and date is not None:
332                text = Date.encode(date)
333            self.text = text  # type: ignore
334            if date_adjust is not None:
335                self.date_adjust = Duration.encode(date_adjust)
date: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
data_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
date_adjust: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarDecl(odfdo.Element):
40class VarDecl(Element):
41    _tag = "text:variable-decl"
42    _properties = (
43        PropDef("name", "text:name"),
44        PropDef("value_type", "office:value-type"),
45    )
46
47    def __init__(
48        self,
49        name: str | None = None,
50        value_type: str | None = None,
51        **kwargs: Any,
52    ) -> None:
53        super().__init__(**kwargs)
54        if self._do_init:
55            if name:
56                self.name = name
57            if value_type:
58                self.value_type = value_type

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarDecl( name: str | None = None, value_type: str | None = None, **kwargs: Any)
47    def __init__(
48        self,
49        name: str | None = None,
50        value_type: str | None = None,
51        **kwargs: Any,
52    ) -> None:
53        super().__init__(**kwargs)
54        if self._do_init:
55            if name:
56                self.name = name
57            if value_type:
58                self.value_type = value_type
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
value_type: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarDecls(odfdo.Element):
36class VarDecls(Element):
37    _tag = "text:variable-decls"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarDescription(odfdo.VarInitialCreator):
504class VarDescription(VarInitialCreator):
505    _tag = "text:description"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
VarInitialCreator
VarInitialCreator
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarFileName(odfdo.Element):
411class VarFileName(Element):
412    _tag = "text:file-name"
413    _properties = (
414        PropDef("display", "text:display"),
415        PropDef("fixed", "text:fixed"),
416    )
417    DISPLAY_VALUE_CHOICE = {  # noqa: RUF012
418        "full",
419        "path",
420        "name",
421        "name-and-extension",
422    }
423
424    def __init__(
425        self,
426        display: str | None = "full",
427        fixed: bool = False,
428        **kwargs: Any,
429    ) -> None:
430        """display can be: 'full', 'path', 'name' or 'name-and-extension'"""
431        super().__init__(**kwargs)
432        if self._do_init:
433            if display not in VarFileName.DISPLAY_VALUE_CHOICE:
434                raise ValueError("unknown display value: %s" % display)
435            self.display = display
436            if fixed:
437                self.fixed = True

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarFileName(display: str | None = 'full', fixed: bool = False, **kwargs: Any)
424    def __init__(
425        self,
426        display: str | None = "full",
427        fixed: bool = False,
428        **kwargs: Any,
429    ) -> None:
430        """display can be: 'full', 'path', 'name' or 'name-and-extension'"""
431        super().__init__(**kwargs)
432        if self._do_init:
433            if display not in VarFileName.DISPLAY_VALUE_CHOICE:
434                raise ValueError("unknown display value: %s" % display)
435            self.display = display
436            if fixed:
437                self.fixed = True

display can be: 'full', 'path', 'name' or 'name-and-extension'

DISPLAY_VALUE_CHOICE = {'path', 'name', 'name-and-extension', 'full'}
display: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarGet(odfdo.ElementTyped):
111class VarGet(ElementTyped):
112    _tag = "text:variable-get"
113    _properties = (
114        PropDef("name", "text:name"),
115        PropDef("style", "style:data-style-name"),
116    )
117
118    def __init__(
119        self,
120        name: str | None = None,
121        value: Any = None,
122        value_type: str | None = None,
123        text: str | None = None,
124        style: str | None = None,
125        **kwargs: Any,
126    ) -> None:
127        super().__init__(**kwargs)
128        if self._do_init:
129            if name:
130                self.name = name
131            if style:
132                self.style = style
133            text = self.set_value_and_type(
134                value=value, value_type=value_type, text=text
135            )
136            self.text = text  # type: ignore

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarGet( name: str | None = None, value: Any = None, value_type: str | None = None, text: str | None = None, style: str | None = None, **kwargs: Any)
118    def __init__(
119        self,
120        name: str | None = None,
121        value: Any = None,
122        value_type: str | None = None,
123        text: str | None = None,
124        style: str | None = None,
125        **kwargs: Any,
126    ) -> None:
127        super().__init__(**kwargs)
128        if self._do_init:
129            if name:
130                self.name = name
131            if style:
132                self.style = style
133            text = self.set_value_and_type(
134                value=value, value_type=value_type, text=text
135            )
136            self.text = text  # type: ignore
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarInitialCreator(odfdo.Element):
443class VarInitialCreator(Element):
444    _tag = "text:initial-creator"
445    _properties = (PropDef("fixed", "text:fixed"),)
446
447    def __init__(self, fixed: bool = False, **kwargs: Any) -> None:
448        super().__init__(**kwargs)
449        if self._do_init and fixed:
450            self.fixed = True

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarInitialCreator(fixed: bool = False, **kwargs: Any)
447    def __init__(self, fixed: bool = False, **kwargs: Any) -> None:
448        super().__init__(**kwargs)
449        if self._do_init and fixed:
450            self.fixed = True
fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarKeywords(odfdo.VarInitialCreator):
525class VarKeywords(VarInitialCreator):
526    _tag = "text:keywords"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
VarInitialCreator
VarInitialCreator
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarPageCount(odfdo.Element):
301class VarPageCount(Element):
302    _tag = "text:page-count"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

Inherited Members
Element
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarPageNumber(odfdo.Element):
270class VarPageNumber(Element):
271    """
272    select_page -- string in ('previous', 'current', 'next')
273
274    page_adjust -- int (to add or subtract to the page number)
275    """
276
277    _tag = "text:page-number"
278    _properties = (
279        PropDef("select_page", "text:select-page"),
280        PropDef("page_adjust", "text:page-adjust"),
281    )
282
283    def __init__(
284        self,
285        select_page: str | None = None,
286        page_adjust: str | None = None,
287        **kwargs: Any,
288    ) -> None:
289        super().__init__(**kwargs)
290        if self._do_init:
291            if select_page is None:
292                select_page = "current"
293            self.select_page = select_page
294            if page_adjust is not None:
295                self.page_adjust = page_adjust

select_page -- string in ('previous', 'current', 'next')

page_adjust -- int (to add or subtract to the page number)

VarPageNumber( select_page: str | None = None, page_adjust: str | None = None, **kwargs: Any)
283    def __init__(
284        self,
285        select_page: str | None = None,
286        page_adjust: str | None = None,
287        **kwargs: Any,
288    ) -> None:
289        super().__init__(**kwargs)
290        if self._do_init:
291            if select_page is None:
292                select_page = "current"
293            self.select_page = select_page
294            if page_adjust is not None:
295                self.page_adjust = page_adjust
select_page: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
page_adjust: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarSet(odfdo.ElementTyped):
 64class VarSet(ElementTyped):
 65    _tag = "text:variable-set"
 66    _properties = (
 67        PropDef("name", "text:name"),
 68        PropDef("style", "style:data-style-name"),
 69        PropDef("display", "text:display"),
 70    )
 71
 72    def __init__(
 73        self,
 74        name: str | None = None,
 75        value: Any = None,
 76        value_type: str | None = None,
 77        display: str | bool = False,
 78        text: str | None = None,
 79        style: str | None = None,
 80        **kwargs: Any,
 81    ) -> None:
 82        super().__init__(**kwargs)
 83        if self._do_init:
 84            if name:
 85                self.name = name
 86            if style:
 87                self.style = style
 88            text = self.set_value_and_type(
 89                value=value, value_type=value_type, text=text
 90            )
 91            if not display:
 92                self.display = "none"
 93            else:
 94                self.text = text  # type: ignore
 95
 96    def set_value(self, value: Any) -> None:
 97        name = self.get_attribute("text:name")
 98        display = self.get_attribute("text:display")
 99        self.clear()
100        text = self.set_value_and_type(value=value)
101        self.set_attribute("text:name", name)
102        if display is not None:
103            self.set_attribute("text:display", display)
104        if isinstance(text, str):
105            self.text = text

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarSet( name: str | None = None, value: Any = None, value_type: str | None = None, display: str | bool = False, text: str | None = None, style: str | None = None, **kwargs: Any)
72    def __init__(
73        self,
74        name: str | None = None,
75        value: Any = None,
76        value_type: str | None = None,
77        display: str | bool = False,
78        text: str | None = None,
79        style: str | None = None,
80        **kwargs: Any,
81    ) -> None:
82        super().__init__(**kwargs)
83        if self._do_init:
84            if name:
85                self.name = name
86            if style:
87                self.style = style
88            text = self.set_value_and_type(
89                value=value, value_type=value_type, text=text
90            )
91            if not display:
92                self.display = "none"
93            else:
94                self.text = text  # type: ignore
def set_value(self, value: Any) -> None:
 96    def set_value(self, value: Any) -> None:
 97        name = self.get_attribute("text:name")
 98        display = self.get_attribute("text:display")
 99        self.clear()
100        text = self.set_value_and_type(value=value)
101        self.set_attribute("text:name", name)
102        if display is not None:
103            self.set_attribute("text:display", display)
104        if isinstance(text, str):
105            self.text = text
name: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
display: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
ElementTyped
set_value_and_type
get_value
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarSubject(odfdo.VarInitialCreator):
518class VarSubject(VarInitialCreator):
519    _tag = "text:subject"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
VarInitialCreator
VarInitialCreator
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarTime(odfdo.Element):
341class VarTime(Element):
342    _tag = "text:time"
343    _properties = (
344        PropDef("time", "text:time-value"),
345        PropDef("fixed", "text:fixed"),
346        PropDef("data_style", "style:data-style-name"),
347        PropDef("time_adjust", "text:time-adjust"),
348    )
349
350    def __init__(
351        self,
352        time: datetime,
353        fixed: bool = False,
354        data_style: str | None = None,
355        text: str | None = None,
356        time_adjust: timedelta | None = None,
357        **kwargs: Any,
358    ) -> None:
359        super().__init__(**kwargs)
360        if self._do_init:
361            self.time = DateTime.encode(time)
362            if fixed:
363                self.fixed = True
364            if data_style is not None:
365                self.data_style = data_style
366            if text is None:
367                text = time.strftime("%H:%M:%S")
368            self.text = text
369            if time_adjust is not None:
370                self.date_adjust = Duration.encode(time_adjust)

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

VarTime( time: datetime.datetime, fixed: bool = False, data_style: str | None = None, text: str | None = None, time_adjust: datetime.timedelta | None = None, **kwargs: Any)
350    def __init__(
351        self,
352        time: datetime,
353        fixed: bool = False,
354        data_style: str | None = None,
355        text: str | None = None,
356        time_adjust: timedelta | None = None,
357        **kwargs: Any,
358    ) -> None:
359        super().__init__(**kwargs)
360        if self._do_init:
361            self.time = DateTime.encode(time)
362            if fixed:
363                self.fixed = True
364            if data_style is not None:
365                self.data_style = data_style
366            if text is None:
367                text = time.strftime("%H:%M:%S")
368            self.text = text
369            if time_adjust is not None:
370                self.date_adjust = Duration.encode(time_adjust)
time: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
data_style: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
time_adjust: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class VarTitle(odfdo.VarInitialCreator):
511class VarTitle(VarInitialCreator):
512    _tag = "text:title"

Super class of all ODF classes.

Representation of an XML element. Abstraction of the XML library behind.

fixed: str | bool | None
396        def getter(self: Element) -> str | bool | None:
397            try:
398                if family and self.family != family:  # type: ignore
399                    return None
400            except AttributeError:
401                return None
402            value = self.__element.get(name)
403            if value is None:
404                return None
405            elif value in ("true", "false"):
406                return Boolean.decode(value)
407            return str(value)
Inherited Members
VarInitialCreator
VarInitialCreator
Element
from_tag
from_tag_for_clone
make_etree_element
tag
elements_repeated_sequence
get_elements
get_element
attributes
get_attribute
get_attribute_integer
get_attribute_string
set_attribute
set_style_attribute
del_attribute
text
text_recursive
tail
search
match
replace
root
parent
is_bound
children
index
text_content
is_empty
get_between
insert
extend
append
delete
replace_element
strip_elements
strip_tags
xpath
clear
clone
serialize
document_body
get_formatted_text
get_styled_elements
dc_creator
dc_date
svg_title
svg_description
get_sections
get_section
get_paragraphs
get_paragraph
get_spans
get_span
get_headers
get_header
get_lists
get_list
get_frames
get_frame
get_images
get_image
get_tables
get_table
get_named_ranges
get_named_range
append_named_range
delete_named_range
get_notes
get_note
get_annotations
get_annotation
get_annotation_ends
get_annotation_end
get_office_names
get_variable_decls
get_variable_decl_list
get_variable_decl
get_variable_sets
get_variable_set
get_variable_set_value
get_user_field_decls
get_user_field_decl_list
get_user_field_decl
get_user_field_value
get_user_defined_list
get_user_defined
get_user_defined_value
get_draw_pages
get_draw_page
get_bookmarks
get_bookmark
get_bookmark_starts
get_bookmark_start
get_bookmark_ends
get_bookmark_end
get_reference_marks_single
get_reference_mark_single
get_reference_mark_starts
get_reference_mark_start
get_reference_mark_ends
get_reference_mark_end
get_reference_marks
get_reference_mark
get_references
get_draw_groups
get_draw_group
get_draw_lines
get_draw_line
get_draw_rectangles
get_draw_rectangle
get_draw_ellipses
get_draw_ellipse
get_draw_connectors
get_draw_connector
get_orphan_draw_connectors
get_tracked_changes
get_changes_ids
get_text_change_deletions
get_text_change_deletion
get_text_change_starts
get_text_change_start
get_text_change_ends
get_text_change_end
get_text_changes
get_text_change
get_tocs
get_toc
get_styles
get_style
class XmlPart:
 37class XmlPart:
 38    """Representation of an XML part.
 39
 40    Abstraction of the XML library behind.
 41    """
 42
 43    def __init__(self, part_name: str, container: Container) -> None:
 44        self.part_name = part_name
 45        self.container = container
 46
 47        # Internal state
 48        self.__tree: _ElementTree | None = None
 49        self.__root: Element | None = None
 50
 51    def _get_tree(self) -> _ElementTree:
 52        if self.__tree is None:
 53            part = self.container.get_part(self.part_name)
 54            self.__tree = parse(BytesIO(part))  # type: ignore
 55        return self.__tree
 56
 57    def __repr__(self) -> str:
 58        return f"<{self.__class__.__name__} part_name={self.part_name}>"
 59
 60    # Public API
 61
 62    @property
 63    def root(self) -> Element:
 64        if self.__root is None:
 65            tree = self._get_tree()
 66            self.__root = Element.from_tag(tree.getroot())
 67        return self.__root
 68
 69    def get_elements(self, xpath_query: str) -> list[Element | Text]:
 70        root = self.root
 71        return root.xpath(xpath_query)
 72
 73    def get_element(self, xpath_query: str) -> Any:
 74        result = self.get_elements(xpath_query)
 75        if not result:
 76            return None
 77        return result[0]
 78
 79    def delete_element(self, child: Element) -> None:
 80        child.delete()
 81
 82    def xpath(self, xpath_query: str) -> list[Element | Text]:
 83        """Apply XPath query to the XML part. Return list of Element or
 84        Text instances translated from the nodes found.
 85        """
 86        root = self.root
 87        return root.xpath(xpath_query)
 88
 89    @property
 90    def clone(self) -> XmlPart:
 91        clone = object.__new__(self.__class__)
 92        for name in self.__dict__:
 93            if name == "container":
 94                setattr(clone, name, self.container.clone)
 95            elif name in ("_XmlPart__tree",):
 96                setattr(clone, name, None)
 97            else:
 98                value = getattr(self, name)
 99                value = deepcopy(value)
100                setattr(clone, name, value)
101        return clone
102
103    def serialize(self, pretty: bool = False) -> bytes:
104        tree = self._get_tree()
105        # Lxml declaration is too exotic to me
106        data = [b'<?xml version="1.0" encoding="UTF-8"?>']
107        bytes_tree = tostring(tree, pretty_print=pretty, encoding="utf-8")
108        # Lxml with pretty_print is adding a empty line
109        if pretty:
110            bytes_tree = bytes_tree.strip()
111        data.append(bytes_tree)
112        return b"\n".join(data)

Representation of an XML part.

Abstraction of the XML library behind.

XmlPart(part_name: str, container: Container)
43    def __init__(self, part_name: str, container: Container) -> None:
44        self.part_name = part_name
45        self.container = container
46
47        # Internal state
48        self.__tree: _ElementTree | None = None
49        self.__root: Element | None = None
part_name
container
root: Element
62    @property
63    def root(self) -> Element:
64        if self.__root is None:
65            tree = self._get_tree()
66            self.__root = Element.from_tag(tree.getroot())
67        return self.__root
def get_elements( self, xpath_query: str) -> list[Element | Text]:
69    def get_elements(self, xpath_query: str) -> list[Element | Text]:
70        root = self.root
71        return root.xpath(xpath_query)
def get_element(self, xpath_query: str) -> Any:
73    def get_element(self, xpath_query: str) -> Any:
74        result = self.get_elements(xpath_query)
75        if not result:
76            return None
77        return result[0]
def delete_element(self, child: Element) -> None:
79    def delete_element(self, child: Element) -> None:
80        child.delete()
def xpath( self, xpath_query: str) -> list[Element | Text]:
82    def xpath(self, xpath_query: str) -> list[Element | Text]:
83        """Apply XPath query to the XML part. Return list of Element or
84        Text instances translated from the nodes found.
85        """
86        root = self.root
87        return root.xpath(xpath_query)

Apply XPath query to the XML part. Return list of Element or Text instances translated from the nodes found.

clone: XmlPart
 89    @property
 90    def clone(self) -> XmlPart:
 91        clone = object.__new__(self.__class__)
 92        for name in self.__dict__:
 93            if name == "container":
 94                setattr(clone, name, self.container.clone)
 95            elif name in ("_XmlPart__tree",):
 96                setattr(clone, name, None)
 97            else:
 98                value = getattr(self, name)
 99                value = deepcopy(value)
100                setattr(clone, name, value)
101        return clone
def serialize(self, pretty: bool = False) -> bytes:
103    def serialize(self, pretty: bool = False) -> bytes:
104        tree = self._get_tree()
105        # Lxml declaration is too exotic to me
106        data = [b'<?xml version="1.0" encoding="UTF-8"?>']
107        bytes_tree = tostring(tree, pretty_print=pretty, encoding="utf-8")
108        # Lxml with pretty_print is adding a empty line
109        if pretty:
110            bytes_tree = bytes_tree.strip()
111        data.append(bytes_tree)
112        return b"\n".join(data)
__version__ = '3.7.4'
def create_table_cell_style( border: str | None = None, border_top: str | None = None, border_bottom: str | None = None, border_left: str | None = None, border_right: str | None = None, padding: str | None = None, padding_top: str | None = None, padding_bottom: str | None = None, padding_left: str | None = None, padding_right: str | None = None, background_color: str | tuple | None = None, shadow: str | None = None, color: str | tuple | None = None) -> Style:
187def create_table_cell_style(
188    border: str | None = None,
189    border_top: str | None = None,
190    border_bottom: str | None = None,
191    border_left: str | None = None,
192    border_right: str | None = None,
193    padding: str | None = None,
194    padding_top: str | None = None,
195    padding_bottom: str | None = None,
196    padding_left: str | None = None,
197    padding_right: str | None = None,
198    background_color: str | tuple | None = None,
199    shadow: str | None = None,
200    color: str | tuple | None = None,
201) -> Style:
202    """Return a cell style.
203
204    The borders arguments must be some style attribute strings or None, see the
205    method 'make_table_cell_border_string' to generate them.
206    If the 'border' argument as the value 'default', the default style
207    "0.06pt solid #000000" is used for the 4 borders.
208    If any value is used for border, it is used for the 4 borders, else any of
209    the 4 borders can be specified by it's own string. If all the border,
210    border_top, border_bottom, ... arguments are None, an empty border is used
211    (ODF value is fo:border="none").
212
213    Padding arguments are string specifying a length (e.g. "0.5mm")". If
214    'padding' is provided, it is used for the 4 sides, else any of
215    the 4 sides padding can be specified by it's own string. Default padding is
216    no padding.
217
218    Arguments:
219
220        border -- str, style string for borders on four sides
221
222        border_top -- str, style string for top if no 'border' argument
223
224        border_bottom -- str, style string for bottom if no 'border' argument
225
226        border_left -- str, style string for left if no 'border' argument
227
228        border_right -- str, style string for right if no 'border' argument
229
230        padding -- str, style string for padding on four sides
231
232        padding_top -- str, style string for top if no 'padding' argument
233
234        padding_bottom -- str, style string for bottom if no 'padding' argument
235
236        padding_left -- str, style string for left if no 'padding' argument
237
238        padding_right -- str, style string for right if no 'padding' argument
239
240        background_color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
241
242        shadow -- str, e.g. "#808080 0.176cm 0.176cm"
243
244        color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
245
246    Return : Style
247    """
248    if border == "default":
249        border = make_table_cell_border_string()  # default border
250    if border is not None:
251        # use the border value for 4 sides.
252        border_bottom = border_top = border_left = border_right = None
253    if (
254        border is None
255        and border_bottom is None
256        and border_top is None
257        and border_left is None
258        and border_right is None
259    ):
260        border = "none"
261    if padding is not None:
262        # use the padding value for 4 sides.
263        padding_bottom = padding_top = padding_left = padding_right = None
264    cell_style = Style(
265        "table-cell",
266        area="table-cell",
267        border=border,
268        border_top=border_top,
269        border_bottom=border_bottom,
270        border_left=border_left,
271        border_right=border_right,
272        padding=padding,
273        padding_top=padding_top,
274        padding_bottom=padding_bottom,
275        padding_left=padding_left,
276        padding_right=padding_right,
277        background_color=background_color,
278        shadow=shadow,
279    )
280    if color:
281        cell_style.set_properties(area="text", color=color)
282    return cell_style

Return a cell style.

The borders arguments must be some style attribute strings or None, see the method 'make_table_cell_border_string' to generate them. If the 'border' argument as the value 'default', the default style "0.06pt solid #000000" is used for the 4 borders. If any value is used for border, it is used for the 4 borders, else any of the 4 borders can be specified by it's own string. If all the border, border_top, border_bottom, ... arguments are None, an empty border is used (ODF value is fo:border="none").

Padding arguments are string specifying a length (e.g. "0.5mm")". If 'padding' is provided, it is used for the 4 sides, else any of the 4 sides padding can be specified by it's own string. Default padding is no padding.

Arguments:

border -- str, style string for borders on four sides

border_top -- str, style string for top if no 'border' argument

border_bottom -- str, style string for bottom if no 'border' argument

border_left -- str, style string for left if no 'border' argument

border_right -- str, style string for right if no 'border' argument

padding -- str, style string for padding on four sides

padding_top -- str, style string for top if no 'padding' argument

padding_bottom -- str, style string for bottom if no 'padding' argument

padding_left -- str, style string for left if no 'padding' argument

padding_right -- str, style string for right if no 'padding' argument

background_color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'

shadow -- str, e.g. "#808080 0.176cm 0.176cm"

color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'

Return : Style

def default_boolean_style() -> Element:
1100def default_boolean_style() -> Element:
1101    return Element.from_tag(
1102        """<number:boolean-style style:name="lpod-default-boolean-style">
1103           <number:boolean/>
1104           </number:boolean-style>"""
1105    )
def default_currency_style() -> Element:
1108def default_currency_style() -> Element:
1109    return Element.from_tag(
1110        """<number:currency-style style:name="lpod-default-currency-style">
1111            <number:text>-</number:text>
1112            <number:number number:decimal-places="2"
1113             number:min-integer-digits="1"
1114             number:grouping="true"/>
1115            <number:text> </number:text>
1116            <number:currency-symbol
1117             number:language="fr"
1118             number:country="FR">€</number:currency-symbol>
1119           </number:currency-style>"""
1120    )
def default_date_style() -> Element:
1087def default_date_style() -> Element:
1088    return Element.from_tag(
1089        """
1090           <number:date-style style:name="lpod-default-date-style">
1091           <number:year number:style="long"/>
1092           <number:text>-</number:text>
1093           <number:month number:style="long"/>
1094           <number:text>-</number:text>
1095           <number:day number:style="long"/>
1096           </number:date-style>"""
1097    )
def default_frame_position_style( name: str = 'FramePosition', horizontal_pos: str = 'from-left', vertical_pos: str = 'from-top', horizontal_rel: str = 'paragraph', vertical_rel: str = 'paragraph') -> Style:
42def default_frame_position_style(
43    name: str = "FramePosition",
44    horizontal_pos: str = "from-left",
45    vertical_pos: str = "from-top",
46    horizontal_rel: str = "paragraph",
47    vertical_rel: str = "paragraph",
48) -> Style:
49    """Helper style for positioning frames in desktop applications that need
50    it.
51
52    Default arguments should be enough.
53
54    Use the returned Style as the frame style or build a new graphic style
55    with this style as the parent.
56    """
57    return Style(
58        family="graphic",
59        name=name,
60        horizontal_pos=horizontal_pos,
61        horizontal_rel=horizontal_rel,
62        vertical_pos=vertical_pos,
63        vertical_rel=vertical_rel,
64    )

Helper style for positioning frames in desktop applications that need it.

Default arguments should be enough.

Use the returned Style as the frame style or build a new graphic style with this style as the parent.

def default_number_style() -> Element:
1055def default_number_style() -> Element:
1056    return Element.from_tag(
1057        """<number:number-style style:name="lpod-default-number-style">
1058           <number:number number:decimal-places="2"
1059            number:min-integer-digits="1"/>
1060           </number:number-style>"""
1061    )
def default_percentage_style() -> Element:
1064def default_percentage_style() -> Element:
1065    return Element.from_tag(
1066        """<number:percentage-style
1067            style:name="lpod-default-percentage-style">
1068           <number:number number:decimal-places="2"
1069            number:min-integer-digits="1"/>
1070           <number:text>%</number:text>
1071           </number:percentage-style>"""
1072    )
def default_time_style() -> Element:
1075def default_time_style() -> Element:
1076    return Element.from_tag(
1077        """<number:time-style style:name="lpod-default-time-style">
1078           <number:hours number:style="long"/>
1079           <number:text>:</number:text>
1080           <number:minutes number:style="long"/>
1081           <number:text>:</number:text>
1082           <number:seconds number:style="long"/>
1083           </number:time-style>"""
1084    )
def default_toc_level_style(level: int) -> Style:
150def default_toc_level_style(level: int) -> Style:
151    """Generate an automatic default style for the given TOC level."""
152    tab_stop = TabStopStyle(style_type="right", leader_style="dotted", leader_text=".")
153    position = 17.5 - (0.5 * level)
154    tab_stop.style_position = f"{position}cm"
155    tab_stops = Element.from_tag("style:tab-stops")
156    tab_stops.append(tab_stop)
157    properties = Element.from_tag("style:paragraph-properties")
158    properties.append(tab_stops)
159    toc_style_level = Style(
160        family="paragraph",
161        name=_toc_entry_style_name(level),
162        parent=f"Contents_20_{level}",
163    )
164    toc_style_level.append(properties)
165    return toc_style_level

Generate an automatic default style for the given TOC level.

def hex2rgb(color: str) -> tuple[int, int, int]:
26def hex2rgb(color: str) -> tuple[int, int, int]:
27    """Turns a "#RRGGBB" hexadecimal color representation into a (R, G, B)
28    tuple.
29
30    Arguments:
31
32        color -- str
33
34    Return: tuple
35    """
36    code = color[1:]
37    if not (len(color) == 7 and color[0] == "#" and code.isalnum()):
38        raise ValueError(f'"{color}" is not a valid color')
39    red = int(code[:2], 16)
40    green = int(code[2:4], 16)
41    blue = int(code[4:6], 16)
42    return (red, green, blue)

Turns a "#RRGGBB" hexadecimal color representation into a (R, G, B) tuple.

Arguments:

color -- str

Return: tuple

def hexa_color(color: str | tuple[int, int, int] | None = None) -> str | None:
 81def hexa_color(color: str | tuple[int, int, int] | None = None) -> str | None:
 82    """Convert a color definition of type tuple or string to hexadecimal
 83    representation.
 84
 85    Empty string is converted to black.
 86    None is converted to None.
 87
 88    Arguments:
 89
 90        color -- str or tuple or None
 91
 92    Return: str or None
 93    """
 94    if color is None:
 95        return None
 96    if isinstance(color, tuple):
 97        return rgb2hex(color)
 98    if not isinstance(color, str):
 99        raise TypeError(f'Invalid color argument "{color!r}"')
100    color = color.strip()
101    if not color:
102        return "#000000"
103    if color.startswith("#"):
104        return color
105    return rgb2hex(color)

Convert a color definition of type tuple or string to hexadecimal representation.

Empty string is converted to black. None is converted to None.

Arguments:

color -- str or tuple or None

Return: str or None

def make_table_cell_border_string( thick: str | float | int | None = None, line: str | None = None, color: str | tuple | None = None) -> str:
167def make_table_cell_border_string(
168    thick: str | float | int | None = None,
169    line: str | None = None,
170    color: str | tuple | None = None,
171) -> str:
172    """Returns a string for style:table-cell-properties fo:border,
173    with default : "0.06pt solid #000000"
174
175        thick -- str or float or int
176        line -- str
177        color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'
178
179    Returns : str
180    """
181    thick_string = _make_thick_string(thick)
182    line_string = _make_line_string(line)
183    color_string = hexa_color(color) or "#000000"
184    return " ".join((thick_string, line_string, color_string))

Returns a string for style:table-cell-properties fo:border, with default : "0.06pt solid #000000"

thick -- str or float or int
line -- str
color -- str or rgb 3-tuple, str is 'black', 'grey', ... or '#012345'

Returns : str

def rgb2hex(color: str | tuple[int, int, int]) -> str:
45def rgb2hex(color: str | tuple[int, int, int]) -> str:
46    """Turns a color name or a (R, G, B) color tuple into a "#RRGGBB"
47    hexadecimal representation.
48
49    Arguments:
50
51        color -- str or tuple
52
53    Return: str
54
55    Examples::
56
57        >>> rgb2hex('yellow')
58        '#FFFF00'
59        >>> rgb2hex((238, 130, 238))
60        '#EE82EE'
61    """
62    if isinstance(color, str):
63        try:
64            code = CSS3_COLORMAP[color.lower()]
65        except KeyError as e:
66            raise KeyError(f'Color "{color}" is unknown in CSS color list') from e
67    elif isinstance(color, tuple):
68        if len(color) != 3:
69            raise ValueError("Color must be a 3-tuple")
70        code = color
71    else:
72        raise TypeError(f'Invalid color "{color}"')
73    for channel in code:
74        if not 0 <= channel <= 255:
75            raise ValueError(
76                f'Invalid color "{color}", channel must be between 0 and 255'
77            )
78    return f"#{code[0]:02X}{code[1]:02X}{code[2]:02X}"

Turns a color name or a (R, G, B) color tuple into a "#RRGGBB" hexadecimal representation.

Arguments:

color -- str or tuple

Return: str

Examples::

>>> rgb2hex('yellow')
'#FFFF00'
>>> rgb2hex((238, 130, 238))
'#EE82EE'